diff --git a/index.js b/index.js index 91557dea..357545c7 100644 --- a/index.js +++ b/index.js @@ -8,11 +8,11 @@ var vfile = require('vfile'); var trough = require('trough'); var string = require('x-is-string'); var func = require('x-is-function'); +var array = require('isarray'); -/* Expose an abstract processor. */ -module.exports = unified().abstract(); +/* Expose a frozen processor. */ +module.exports = unified().freeze(); -/* Methods. */ var slice = [].slice; /* Process pipeline. */ @@ -40,13 +40,13 @@ function unified() { var attachers = []; var transformers = trough(); var namespace = {}; - var concrete = true; + var frozen = false; /* Data management. */ processor.data = data; /* Lock. */ - processor.abstract = abstract; + processor.freeze = freeze; /* Plug-ins. */ processor.attachers = attachers; @@ -79,17 +79,49 @@ function unified() { return destination; } - /* Abstract: used to signal an abstract processor which - * should made concrete before using. + /* Freeze: used to signal a processor that has finished + * configuration. * - * For example, take unified itself. It’s abstract. + * For example, take unified itself. It’s frozen. * Plug-ins should not be added to it. Rather, it should - * be made concrete (by invoking it) before modifying it. + * be extended, by invoking it, before modifying it. * * In essence, always invoke this when exporting a * processor. */ - function abstract() { - concrete = false; + function freeze() { + var length = attachers.length; + var index = -1; + var values; + var plugin; + var options; + var transformer; + + if (frozen) { + return processor; + } + + while (++index < length) { + values = attachers[index]; + plugin = values[0]; + options = values[1]; + transformer = null; + + if (options === false) { + return; + } + + if (options === true) { + values[1] = undefined; + } + + transformer = plugin.apply(processor, values.slice(1)); + + if (func(transformer)) { + transformers.use(transformer); + } + } + + frozen = true; return processor; } @@ -97,11 +129,11 @@ function unified() { /* Data management. * Getter / setter for processor-specific informtion. */ function data(key, value) { - assertConcrete('data', concrete); - if (string(key)) { /* Set `key`. */ if (arguments.length === 2) { + assertUnfrozen('data', frozen); + namespace[key] = value; return processor; @@ -111,15 +143,15 @@ function unified() { return (has(namespace, key) && namespace[key]) || null; } - /* Get space. */ - if (!key) { - return namespace; - } - /* Set space. */ - namespace = key; + if (key) { + assertUnfrozen('data', frozen); + namespace = key; + return processor; + } - return processor; + /* Get space. */ + return namespace; } /* Plug-in management. @@ -130,78 +162,100 @@ function unified() { * * a tuple of one attacher and options. * * a matrix: list containing any of the above and * matrices. - * * a processor: another processor to use all its - * plugins (except parser if there’s already one). * * a preset: an object with `plugins` (any of the * above, optional), and `settings` (object, optional). */ function use(value) { - var args = slice.call(arguments, 0); - var params = args.slice(1); - var parser; - var index; - var length; - var transformer; - var result; + var settings; - assertConcrete('use', concrete); + assertUnfrozen('use', frozen); - if (!func(value)) { - /* Multiple attachers. */ + if (value === null || value === undefined) { + /* empty */ + } else if (func(value)) { + addPlugin.apply(null, arguments); + } else if (typeof value === 'object') { if ('length' in value) { - index = -1; - length = value.length; - - if (!func(value[0])) { - /* Matrix of things. */ - while (++index < length) { - use(value[index]); - } - } else if (func(value[1])) { - /* List of things. */ - while (++index < length) { - use.apply(null, [value[index]].concat(params)); - } + addList(value); + } else { + addPreset(value); + } + } else { + throw new Error('Expected usable value, not `' + value + '`'); + } + + if (settings) { + namespace.settings = extend(namespace.settings || {}, settings); + } + + return processor; + + function addPreset(result) { + addList(result.plugins); + + if (result.settings) { + settings = extend(settings || {}, result.settings); + } + } + + function add(value) { + if (func(value)) { + addPlugin(value); + } else if (typeof value === 'object') { + if ('length' in value) { + addPlugin.apply(null, value); } else { - /* Arguments. */ - use.apply(null, value); + addPreset(value); } - - return processor; + } else { + throw new Error('Expected usable value, not `' + value + '`'); } + } - /* Preset. */ - use(value.plugins || []); - namespace.settings = extend(namespace.settings || {}, value.settings || {}); + function addList(plugins) { + var length; + var index; - return processor; + if (plugins === null || plugins === undefined) { + /* empty */ + } else if (array(plugins)) { + length = plugins.length; + index = -1; + + while (++index < length) { + add(plugins[index]); + } + } else { + throw new Error('Expected a list of plugins, not `' + plugins + '`'); + } } - /* Store attacher. */ - attachers.push(args); + function addPlugin(plugin, value) { + var entry = find(plugin); - /* Use a processor (except its parser if there’s already one. - * Note that the processor is stored on `attachers`, making - * it possibly mutating in the future, but also ensuring - * the parser isn’t overwritten in the future either. */ - if (proc(value)) { - parser = processor.Parser; - result = use(value.attachers); + if (entry) { + if (value !== false && entry[1] !== false && !array(value)) { + value = extend(entry[1], value); + } - if (parser) { - processor.Parser = parser; + entry[1] = value; + } else { + attachers.push(slice.call(arguments)); } - - return result; } + } + + function find(plugin) { + var length = attachers.length; + var index = -1; + var entry; - /* Single attacher. */ - transformer = value.apply(processor, params); + while (++index < length) { + entry = attachers[index]; - if (func(transformer)) { - transformers.use(transformer); + if (entry[0] === plugin) { + return entry; + } } - - return processor; } /* Parse a file (in string or VFile representation) @@ -211,7 +265,7 @@ function unified() { var Parser = processor.Parser; var file = vfile(doc); - assertConcrete('parse', concrete); + freeze(); assertParser('parse', Parser); if (newable(Parser)) { @@ -224,8 +278,8 @@ function unified() { /* Run transforms on a Unist node representation of a file * (in string or VFile representation), async. */ function run(node, file, cb) { - assertConcrete('run', concrete); assertNode(node); + freeze(); if (!cb && func(file)) { cb = file; @@ -260,8 +314,6 @@ function unified() { var complete = false; var result; - assertConcrete('runSync', concrete); - run(node, file, done); assertDone('runSync', 'run', complete); @@ -282,7 +334,7 @@ function unified() { var Compiler = processor.Compiler; var file = vfile(doc); - assertConcrete('stringify', concrete); + freeze(); assertCompiler('stringify', Compiler); assertNode(node); @@ -299,7 +351,7 @@ function unified() { * resulting node using the `Compiler` on the processor, * and store that result on the VFile. */ function process(doc, cb) { - assertConcrete('process', concrete); + freeze(); assertParser('process', processor.Parser); assertCompiler('process', processor.Compiler); @@ -332,7 +384,7 @@ function unified() { var complete = false; var file; - assertConcrete('processSync', concrete); + freeze(); assertParser('processSync', processor.Parser); assertCompiler('processSync', processor.Compiler); file = vfile(doc); @@ -350,11 +402,6 @@ function unified() { } } -/* Check if `processor` is a unified processor. */ -function proc(processor) { - return func(processor) && func(processor.use) && func(processor.process); -} - /* Check if `func` is a constructor. */ function newable(value) { return func(value) && keys(value.prototype); @@ -383,12 +430,12 @@ function assertCompiler(name, Compiler) { } } -/* Assert the processor is concrete. */ -function assertConcrete(name, concrete) { - if (!concrete) { +/* Assert the processor is not frozen. */ +function assertUnfrozen(name, frozen) { + if (frozen) { throw new Error( - 'Cannot invoke `' + name + '` on abstract processor.\n' + - 'To make the processor concrete, invoke it: ' + + 'Cannot invoke `' + name + '` on a frozen processor.\n' + + 'Create a new processor first, by invoking it: ' + 'use `processor()` instead of `processor`.' ); } diff --git a/package.json b/package.json index 1bd8f878..1842ab92 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "bail": "^1.0.0", "extend": "^3.0.0", "has": "^1.0.1", + "isarray": "^2.0.1", "trough": "^1.0.0", "vfile": "^2.0.0", "x-is-function": "^1.0.4", diff --git a/readme.md b/readme.md index 19903643..8eb2f7da 100644 --- a/readme.md +++ b/readme.md @@ -65,7 +65,7 @@ no issues found * [processor.process(file|value\[, done\])](#processorprocessfilevalue-done) * [processor.processSync(file|value)](#processorprocesssyncfilevalue) * [processor.data(key\[, value\])](#processordatakey-value) - * [processor.abstract()](#processorabstract) + * [processor.freeze()](#processorfreeze) * [Plugin](#plugin) * [function attacher(\[options\])](#function-attacheroptions) * [function transformer(node, file\[, next\])](#function-transformernode-file-next) @@ -102,9 +102,9 @@ the descendant processor is configured in the future, that configuration does not change the ancestral processor. Often, when processors are exposed from a library (for example, -unified itself), they should not be modified directly, as that +unified itself), they should not be configured directly, as that would change their behaviour for all users. Those processors are -[**abstract**][abstract], and they should be made concrete before +[**frozen**][freeze], and new processors should be made from them before they are used, by invoking them. ###### Node @@ -220,7 +220,7 @@ Object describing how to process text. ###### Returns -`Function` — A new [**concrete**][abstract] processor which is +`Function` — A new [**unfrozen**][freeze] processor which is configured to function the same as its ancestor. But, when the descendant processor is configured in the future, that configuration does not change the ancestral processor. @@ -247,24 +247,16 @@ that plug-in with optional options. ###### Signatures * `processor.use(plugin[, options])`; -* `processor.use(plugins[, options])`; -* `processor.use(list)`; -* `processor.use(matrix)`; * `processor.use(preset)`; -* `processor.use(processor)`. +* `processor.use(list)`; ###### Parameters * `plugin` ([`Plugin`][plugin]); * `options` (`*`, optional) — Configuration for `plugin`. -* `plugins` (`Array.`) — List of plugins; -* `list` (`Array`) — `plugin` and `options` in an array; -* `matrix` (`Array`) — Array where each entry is a `list`; -* `preset` (`Object`) — Object with an optional `plugins` - (set to `plugins`, `list`, or `matrix`), and/or an optional - `settings` object; -* `processor` ([`Processor`][processor]) — Other processor whose - plugins to use (except for a parser). +* `preset` (`Object`) — Object with an optional `plugins` (set to `list`), + and/or an optional `settings` object; +* `list` (`Array`) — plugins, presets, and arguments, in an array. ###### Returns @@ -279,22 +271,14 @@ gives an overview. var unified = require('unified'); unified() - // Single plugin: - .use(plugin) // Plugin with options: .use(plugin, {}) // Plugins: .use([plugin, pluginB]) - // Plugins with the same options: - .use([plugin, pluginB], {}) - // List: - .use([plugin, {}]) - // Matrix of lists: - .use([[plugin, {}], [pluginB, {}]]) - // Preset with plugins: - .use({plugins: [plugin, pluginB]}) - // Preset with matrix and settings: - .use({plugins: [[plugin, {}], [pluginB, {}]], settings: {position: false}}) + // Two plugins, the second with options: + .use([plugin, [pluginB, {}]]) + // Preset with plugins and settings: + .use({plugins: [plugin, [pluginB, {}]], settings: {position: false}}) // Settings only: .use({settings: {position: false}}); @@ -552,19 +536,17 @@ Yields: bravo ``` -### `processor.abstract()` +### `processor.freeze()` -Turn a processor into an abstract processor. Abstract processors -are meant to be extended, and not to be configured or processed -directly (as concrete processors are). +Freeze a processor. Frozen processors are meant to be extended, and not to +be configured or processed directly. -Once a processor is abstract, it cannot be made concrete again. -But, a new concrete processor functioning just like it can be -created by invoking the processor. +Once a processor is frozen, it cannot be unfrozen. But, a new processor +functioning just like it can be created by invoking the processor. ###### Returns -`Processor` — The processor on which `abstract` is invoked. +`Processor` — The processor on which `freeze` is invoked. ###### Example @@ -576,49 +558,49 @@ var unified = require('unified'); var parse = require('rehype-parse'); var stringify = require('rehype-stringify'); -module.exports = unified().use(parse).use(stringify).abstract(); +module.exports = unified().use(parse).use(stringify).freeze(); ``` -The below example, `a.js`, shows how that processor can be used to -create a command line interface which reformats HTML passed on -**stdin**(4) and outputs it on **stdout**(4). +The below example, `a.js`, shows how that processor can be used and +configured. ```js var rehype = require('rehype'); -var concat = require('concat-stream'); +var format = require('rehype-format'); +// ... -process.stdin.pipe(concat(function (buf) { - process.stdout.write(rehype().processSync(buf)) -})); +rehype() + .use(format) + // ... ``` The below example, `b.js`, shows a similar looking example which -operates on the abstract [**rehype**][rehype] interface. If this +operates on the frozen [**rehype**][rehype] interface. If this behaviour was allowed it would result in unexpected behaviour, so an error is thrown. **This is invalid**: ```js var rehype = require('rehype'); -var concat = require('concat-stream'); +var format = require('rehype-format'); +// ... -process.stdin.pipe(concat(function (buf) { - process.stdout.write(rehype.processSync(buf)); -})); +rehype + .use(format) + // ... ``` Yields: ```txt -~/index.js:154 - throw new Error( - ^ - -Error: Cannot invoke `process` on abstract processor. -To make the processor concrete, invoke it: use `processor()` instead of `processor`. - at assertConcrete (~/index.js:154:13) - at Function.process (~/index.js:421:5) - at ~/b.js:5:31 - ... +~/node_modules/unified/index.js:436 + throw new Error( + ^ + +Error: Cannot invoke `use` on a frozen processor. +Create a new processor first, by invoking it: use `processor()` instead of `processor`. + at assertUnfrozen (~/node_modules/unified/index.js:436:11) + at Function.use (~/node_modules/unified/index.js:170:5) + at Object. (~/b.js:6:4) ``` ## `Plugin` @@ -866,7 +848,7 @@ remark().use(preset).process(vfile.readSync('index.md'), function (err, file) { [next]: #function-nexterr-tree-file -[abstract]: #processorabstract +[freeze]: #processorfreeze [plugin]: #plugin diff --git a/test/abstract.js b/test/abstract.js deleted file mode 100644 index aea32a51..00000000 --- a/test/abstract.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -var test = require('tape'); -var unified = require('..'); - -test('abstract()', function (t) { - var abstract = unified().abstract(); - var concrete = unified(); - - t.doesNotThrow( - function () { - concrete.data(); - }, - '`data` can be invoked on concrete interfaces' - ); - - t.throws( - function () { - abstract.data(); - }, - /Cannot invoke `data` on abstract processor/, - '`data` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.use(); - }, - /Cannot invoke `use` on abstract processor/, - '`use` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.parse(); - }, - /Cannot invoke `parse` on abstract processor/, - '`parse` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.stringify(); - }, - /Cannot invoke `stringify` on abstract processor/, - '`stringify` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.runSync(); - }, - /Cannot invoke `runSync` on abstract processor/, - '`runSync` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.run(); - }, - /Cannot invoke `run` on abstract processor/, - '`run` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.processSync(); - }, - /Cannot invoke `processSync` on abstract processor/, - '`processSync` cannot be invoked on abstract interfaces' - ); - - t.throws( - function () { - abstract.process(); - }, - /Cannot invoke `process` on abstract processor/, - '`process` cannot be invoked on abstract interfaces' - ); - - t.end(); -}); diff --git a/test/core.js b/test/core.js index 3c40052c..55d1cf0d 100644 --- a/test/core.js +++ b/test/core.js @@ -12,8 +12,8 @@ test('unified()', function (t) { function () { unified.use(Function.prototype); }, - /Cannot invoke `use` on abstract processor/, - 'should be abstract' + /Cannot invoke `use` on a frozen processor/, + 'should be frozen' ); p = unified(); @@ -26,7 +26,7 @@ test('unified()', function (t) { }); count = 0; - q = p(); + q = p().freeze(); t.equal( count, diff --git a/test/freeze.js b/test/freeze.js new file mode 100644 index 00000000..3ad6dc16 --- /dev/null +++ b/test/freeze.js @@ -0,0 +1,82 @@ +'use strict'; + +var test = require('tape'); +var simple = require('./util/simple'); +var unified = require('..'); + +test('freeze()', function (t) { + var frozen = unified().use(config).freeze(); + var unfrozen = frozen(); + + function config() { + this.Parser = simple.Parser; + this.Compiler = simple.Compiler; + } + + t.doesNotThrow( + function () { + unfrozen.data(); + }, + '`data` can be invoked on unfrozen interfaces' + ); + + t.throws( + function () { + frozen.data('foo', 'bar'); + }, + /Cannot invoke `data` on a frozen processor/, + '`data` cannot be invoked on frozen interfaces' + ); + + t.throws( + function () { + frozen.use(); + }, + /Cannot invoke `use` on a frozen processor/, + '`use` cannot be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.parse(); + }, + '`parse` can be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.stringify({type: 'foo'}); + }, + '`stringify` can be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.runSync({type: 'foo'}); + }, + '`runSync` can be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.run({type: 'foo'}, function () {}); + }, + '`run` can be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.processSync(''); + }, + '`processSync` can be invoked on frozen interfaces' + ); + + t.doesNotThrow( + function () { + frozen.process('', function () {}); + }, + '`process` can be invoked on frozen interfaces' + ); + + t.end(); +}); diff --git a/test/index.js b/test/index.js index 514dd815..698635e6 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,7 @@ 'use strict'; require('./core'); -require('./abstract'); +require('./freeze'); require('./data'); require('./use'); require('./parse'); diff --git a/test/use.js b/test/use.js index f01be590..2b9375c9 100644 --- a/test/use.js +++ b/test/use.js @@ -5,99 +5,273 @@ var unified = require('..'); /* Plugins. */ test('use(plugin[, options])', function (t) { - var p = unified(); - var o = {}; - var n; + t.test('should ignore missing values', function (st) { + var p = unified(); + st.equal(p.use(), p, 'missing'); + st.equal(p.use(null), p, '`null`'); + st.equal(p.use(undefined), p, '`undefined`'); + st.end(); + }); - t.plan(11); + t.test('should throw when given invalid values', function (st) { + st.throws( + function () { + unified().use(false); + }, + /^Error: Expected usable value, not `false`$/, + '`false`' + ); - p.use(function (options) { - t.equal(this, p, 'should invoke a plugin with `processor` as the context'); - t.equal(options, o, 'should invoke a plugin with `options`'); - }, o); + st.throws( + function () { + unified().use(true); + }, + /^Error: Expected usable value, not `true`$/, + '`true`' + ); - p.use([ - function () { - t.equal(this, p, 'should support a list of plugins (#1)'); - }, - function () { - t.equal(this, p, 'should support a list of plugins (#2)'); + st.throws( + function () { + unified().use('alfred'); + }, + /^Error: Expected usable value, not `alfred`$/, + '`string`' + ); + + st.end(); + }); + + t.test('should support plugin and options', function (st) { + var p = unified(); + var o = {}; + + st.plan(2); + + p + .use(function (options) { + st.equal(this, p, 'should invoke a plugin with `processor` as the context'); + st.equal(options, o, 'should invoke a plugin with `options`'); + }, o) + .freeze(); + }); + + t.test('should support a list of plugins', function (st) { + var p = unified(); + + st.plan(2); + + p + .use([ + function () { + st.equal(this, p, 'should support a list of plugins (#1)'); + }, + function () { + st.equal(this, p, 'should support a list of plugins (#2)'); + } + ]) + .freeze(); + }); + + t.test('should support a list of one plugin', function (st) { + var p = unified(); + + st.plan(1); + + p + .use([ + function () { + st.equal(this, p, 'should support a list of plugins (#2)'); + } + ]) + .freeze(); + }); + + t.test('should support a list of plugins and arguments', function (st) { + var p = unified(); + var o = {}; + + st.plan(2); + + p + .use([ + [function (options) { + st.equal(options, o, 'should support arguments with options'); + }, o], + [function () { + st.equal(this, p, 'should support a arguments without options'); + }] + ]) + .freeze(); + }); + + t.test('should throw when given invalid values in lists', function (st) { + st.throws( + function () { + unified().use([false]); + }, + /^Error: Expected usable value, not `false`$/, + '`false`' + ); + + st.throws( + function () { + unified().use([true]); + }, + /^Error: Expected usable value, not `true`$/, + '`true`' + ); + + st.throws( + function () { + unified().use(['alfred']); + }, + /^Error: Expected usable value, not `alfred`$/, + '`string`' + ); + + st.end(); + }); + + t.test('should reconfigure', function (st) { + var p = unified(); + + st.plan(1); + + p + .use([ + [plugin, {foo: true, bar: true}], + [plugin, {foo: false, qux: true}] + ]) + .freeze(); + + function plugin(options) { + st.deepEqual(options, {foo: false, bar: true, qux: true}, 'should reconfigure'); } - ]); - - p.use([function () { - t.equal(this, p, 'should support a list of one plugin'); - }]); - - p.use([function (options) { - t.equal(options, o, 'should support a plugin--options tuple'); - }, o]); - - p.use([ - [function (options) { - t.equal(options, o, 'should support a matrix (#1)'); - }, o], - [function () { - t.equal(this, p, 'should support a matrix (#2)'); - }] - ]); - - n = {type: 'test'}; - - p.use(function () { - return function (node, file) { - t.equal(node, n, 'should attach a transformer (#1)'); - t.ok('message' in file, 'should attach a transformer (#2)'); - - throw new Error('Alpha bravo charlie'); - }; }); + t.test('should reconfigure to turn off', function (st) { + var p = unified(); + + st.doesNotThrow(function () { + p + .use([ + [plugin], + [plugin, false] + ]) + .freeze(); + + function plugin() { + throw new Error('Error'); + } + }); + + st.end(); + }); + + t.test('should reconfigure to turn on (boolean)', function (st) { + var p = unified(); + + st.plan(1); + + p + .use([ + [plugin, false], + [plugin, true] + ]) + .freeze(); + + function plugin() { + st.pass('should reconfigure'); + } + }); + + t.test('should reconfigure to turn on (options)', function (st) { + var p = unified(); + + st.plan(1); + + p + .use([ + [plugin, false], + [plugin, {foo: true}] + ]) + .freeze(); + + function plugin(options) { + st.deepEqual(options, {foo: true}, 'should reconfigure'); + } + }); + + t.test('should attach transformers', function (st) { + var p = unified(); + var n = {type: 'test'}; + + st.plan(3); + + p + .use(function () { + return function (node, file) { + st.equal(node, n, 'should attach a transformer (#1)'); + st.ok('message' in file, 'should attach a transformer (#2)'); + + throw new Error('Alpha bravo charlie'); + }; + }) + .freeze(); + + st.throws( + function () { + p.runSync(n); + }, + /Error: Alpha bravo charlie/, + 'should attach a transformer (#3)' + ); + }); + + t.end(); +}); + +test('use(preset)', function (t) { t.throws( function () { - p.runSync(n); + unified().use({plugins: false}); }, - /Error: Alpha bravo charlie/, - 'should attach a transformer (#3)' + /^Error: Expected a list of plugins, not `false`$/, + 'should throw on invalid `plugins` (1)' ); - t.end(); -}); + t.throws( + function () { + unified().use({plugins: {foo: true}}); + }, + /^Error: Expected a list of plugins, not `\[object Object]`$/, + 'should throw on invalid `plugins` (2)' + ); -test('use(preset)', function (t) { t.test('should support empty presets', function (st) { - var p = unified(); - - st.equal(p.use({}), p, 'should support empty presets (1)'); - st.equal(p.attachers.length, 0, 'should support empty presets (2)'); + var p = unified().use({}).freeze(); + st.equal(p.attachers.length, 0); st.end(); }); t.test('should support presets with empty plugins', function (st) { - var p = unified(); - var preset = {plugins: []}; - - st.equal(p.use(preset), p, 'should support presets with empty plugins (1)'); - st.equal(p.attachers.length, 0, 'should support presets with empty plugins (2)'); + var p = unified().use({plugins: []}).freeze(); + st.equal(p.attachers.length, 0); st.end(); }); t.test('should support presets with empty settings', function (st) { - var p = unified(); - var preset = {settings: {}}; - - st.equal(p.use(preset), p, 'should support presets with empty settings (1)'); - st.deepEqual(p.data(), {settings: {}}, 'should support presets with empty settings (2)'); + var p = unified().use({settings: {}}).freeze(); + st.deepEqual(p.data(), {settings: {}}); st.end(); }); t.test('should support presets with a plugin', function (st) { - var p = unified(); - var preset = {plugins: [plugin]}; + st.plan(2); - st.plan(3); - st.equal(p.use(preset), p, 'should support presets with a plugin (1)'); - st.equal(p.attachers.length, 1, 'should support presets with a plugin (2)'); + var p = unified().use({plugins: [plugin]}).freeze(); + + st.equal(p.attachers.length, 1); function plugin() { st.pass(); @@ -105,12 +279,10 @@ test('use(preset)', function (t) { }); t.test('should support presets with plugins', function (st) { - var p = unified(); - var preset = {plugins: [a, b]}; + var p = unified().use({plugins: [a, b]}).freeze(); - st.plan(4); - st.equal(p.use(preset), p, 'should support presets with plugins (1)'); - st.equal(p.attachers.length, 2, 'should support presets with plugins (2)'); + st.plan(3); + st.equal(p.attachers.length, 2); function a() { st.pass(); @@ -122,11 +294,8 @@ test('use(preset)', function (t) { }); t.test('should support presets with settings', function (st) { - var p = unified(); - var preset = {settings: {foo: true}}; - - st.equal(p.use(preset), p, 'should support presets with settings (1)'); - st.deepEqual(p.data('settings'), {foo: true}, 'should support presets with settings (2)'); + var p = unified().use({settings: {foo: true}}).freeze(); + st.deepEqual(p.data('settings'), {foo: true}); st.end(); }); @@ -136,139 +305,67 @@ test('use(preset)', function (t) { .use({settings: {qux: true, foo: false}}) .data(); - st.deepEqual( - data.settings, - {foo: false, bar: true, qux: true}, - 'should merge multiple presets with settings' - ); - + st.deepEqual(data.settings, {foo: false, bar: true, qux: true}); st.end(); }); t.test('should support extending presets', function (st) { - var p = unified().use({settings: {alpha: true}, plugins: [a, b]}); - var q = p(); + var p = unified().use({settings: {alpha: true}, plugins: [a, b]}).freeze(); + var q = p().freeze(); st.plan(7); - st.equal(p.attachers.length, 2, 'should support extending presets (1)'); - st.equal(q.attachers.length, 2, 'should support extending presets (2)'); - st.deepEqual(q.data('settings'), {alpha: true}, 'should support extending presets (3)'); + st.equal(p.attachers.length, 2, '1'); + st.equal(q.attachers.length, 2, '2'); + st.deepEqual(q.data('settings'), {alpha: true}, '3'); function a() { - st.pass(); + st.pass('a'); } function b() { - st.pass(); - } - }); - - t.test('should support presets with plugins as a tuple', function (st) { - var opts = {}; - var p = unified().use({plugins: [plugin, opts]}); - var q = p(); - - st.plan(4); - st.equal(p.attachers.length, 1, 'should support tuples (1)'); - st.equal(q.attachers.length, 1, 'should support tuples (2)'); - - function plugin(options) { - st.equal(options, opts, 'should pass options to plugin'); + st.pass('b'); } }); t.test('should support presets with plugins as a matrix', function (st) { var one = {}; var two = {}; - var p = unified().use({plugins: [[a, one], [b, two]]}); - var q = p(); + var p = unified().use({plugins: [[a, one], [b, two]]}).freeze(); + var q = p().freeze(); st.plan(6); - st.equal(p.attachers.length, 2, 'should support matrices (1)'); - st.equal(q.attachers.length, 2, 'should support matrices (2)'); + st.equal(p.attachers.length, 2, '1'); + st.equal(q.attachers.length, 2, '2'); function a(options) { - st.equal(options, one, 'should pass options to plugin (1)'); + st.equal(options, one, 'a'); } function b(options) { - st.equal(options, two, 'should pass options to plugin (2)'); + st.equal(options, two, 'b'); } }); - t.end(); -}); - -/* Processors. */ -test('use(processor)', function (t) { - var p = unified(); - var q = unified(); - var o = {}; - var n; - var res; - var fixture; - - t.plan(12); - - res = p.use(q); - - t.equal(res, p, 'should return the origin processor'); - t.equal(p.attachers[0][0], q, 'should store attachers'); - - p = unified(); - q = unified(); - - fixture = q; - - q.use(function (options) { - t.equal(this, fixture, 'should invoke a plugin with `processor` as the context'); - t.equal(options, o, 'should invoke a plugin with `options`'); - }, o); - - fixture = p; - - p.use(q); - - q = unified(); - p = unified().use(q); - n = {type: 'test'}; - - q.use(function () { - return function (node, file) { - t.equal(node, n, 'should attach a transformer (#1)'); - t.ok('message' in file, 'should attach a transformer (#2)'); - - throw new Error('Alpha bravo charlie'); - }; - }); - - p.use(q); + t.test('should support nested presets', function (st) { + var one = {}; + var two = {}; + var p1 = {plugins: [[a, one]]}; + var p2 = {plugins: [[b, two]]}; + var p = unified().use({plugins: [p1, p2]}).freeze(); + var q = p().freeze(); - t.throws( - function () { - p.runSync(n); - }, - /Error: Alpha bravo charlie/, - 'should attach a transformer (#3)' - ); + st.plan(6); + st.equal(p.attachers.length, 2, '1'); + st.equal(q.attachers.length, 2, '2'); - p = unified().use(function () { - this.Parser = ParserA; - }); + function a(options) { + st.equal(options, one, 'a'); + } - q = unified().use(function () { - this.Parser = ParserB; + function b(options) { + st.equal(options, two, 'b'); + } }); - t.equal(p.Parser, ParserA); - t.equal(q.Parser, ParserB); - - p.use(q); - - t.equal(p.Parser, ParserA); - - function ParserA() {} - function ParserB() {} - t.end(); });