diff --git a/.editorconfig b/.editorconfig index 991900b..818e072 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,3 @@ -# http://editorconfig.org root = true [*] @@ -9,14 +8,6 @@ indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true -[*.md] +[{**/{actual,fixtures,expected,templates}/**,*.md}] trim_trailing_whitespace = false -insert_final_newline = false - -[test/**] -trim_trailing_whitespace = false -insert_final_newline = false - -[templates/**] -trim_trailing_whitespace = false -insert_final_newline = false +insert_final_newline = false \ No newline at end of file diff --git a/.eslintrc b/.eslintrc.json similarity index 95% rename from .eslintrc rename to .eslintrc.json index 7b5d047..948dbdb 100644 --- a/.eslintrc +++ b/.eslintrc.json @@ -34,6 +34,7 @@ "handle-callback-err": [2, "^(err|error)$" ], "indent": [2, 2, { "SwitchCase": 1 }], "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "keyword-spacing": [2, { "before": true, "after": true }], "new-cap": [2, { "newIsCap": true, "capIsNew": false }], "new-parens": 2, "no-array-constructor": 2, @@ -49,7 +50,6 @@ "no-dupe-keys": 2, "no-duplicate-case": 2, "no-empty-character-class": 2, - "no-empty-label": 2, "no-eval": 2, "no-ex-assign": 2, "no-extend-native": 2, @@ -71,7 +71,7 @@ "no-multi-spaces": 2, "no-multi-str": 2, "no-multiple-empty-lines": [2, { "max": 1 }], - "no-native-reassign": 2, + "no-native-reassign": 0, "no-negated-in-lhs": 2, "no-new": 2, "no-new-func": 2, @@ -108,13 +108,10 @@ "radix": 2, "semi": [2, "always"], "semi-spacing": [2, { "before": false, "after": true }], - "space-after-keywords": [2, "always"], "space-before-blocks": [2, "always"], "space-before-function-paren": [2, "never"], - "space-before-keywords": [2, "always"], "space-in-parens": [2, "never"], "space-infix-ops": 2, - "space-return-throw-case": 2, "space-unary-ops": [2, { "words": true, "nonwords": false }], "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], "use-isnan": 2, diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..3957f52 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,45 @@ +# Contributing to Generate + +First and foremost, thank you! We appreciate that you want to contribute to Generate, your time is valuable, and your contributions mean a lot to us. + +**What does "contributing" mean?** + +Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: + +- Updating or correcting documentation +- Feature requests +- Bug reports + +The [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) also has good advice. + +## Issues + +**Before creating an issue** + +Please make sure you're creating one in the right place: + +- do you have a template syntax question? Like how to accomplish something with handlebars? The best place to get answers for this is [stackoverflow.com](https://github.com/stackoverflow.com), the [handlebars docs](handlebarsjs.com), or the documentation for the template engine you're using. +- Are you having an issue with an Generate feature that is powered by an underlying lib? This is sometimes difficult to know, but sometimes it can be pretty easy to find out. For example, if you use a glob pattern somewhere and you found what you believe to be a matching bug, that would probably be an issue for [node-glob][] or [micromatch][] + +**Creating an issue** + +Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: + +- what version of assemble are you using? +- is the issue helper-related? If so, this issue should probably be opened on the repo related to the helper being used. +- do you have any custom helpers defined? Is the issue related to the helper itself, data (context) being passed to the helper, or actually registering the helper in the first place? +- are you using middleware? +- any plugins? + + +## Above and beyond + +Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. + +- take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). +- Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). +- use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others +- use syntax highlighting by adding the correct language name after the first "code fence" + +[node-glob]: https://github.com/isaacs/node-glob +[micromatch]: https://github.com/jonschlinkert/micromatch diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..1409b5d --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,13 @@ +Before opening an issue, please: + +- read the [contributing guidelines](https://github.com/generate/generate/blob/master/.github/contributing.md) +- [search for existing duplicate or closed issues](https://github.com/generate/generate/issues?utf8=%E2%9C%93&q=is%3Aissue) +- Do not open issues to ask for implementation help or to ask "how to" questions here. Instead ask on [StackOverflow](stackoverflow.com) or one of the services listed on the [readme](https://github.com/generate/generate/blob/master/README.md#community) + +For bug reports, please provide the following details: + +- **version**: what version of generate you were using when you experienced the bug? +- **[reduced test case](https://css-tricks.com/reduced-test-cases/)**: the minimum amount of detail and code to reproduce the bug +- **error messages**: please paste any error reports into the issue or a gist + +Please wrap all code and error messages in [markdown code fences](https://help.github.com/articles/creating-and-highlighting-code-blocks/). \ No newline at end of file diff --git a/.gitignore b/.gitignore index 80a228c..7988154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,21 @@ +# always ignore files *.DS_Store *.sublime-* -_gh_pages -bower_components + +# test related, or directories generated by tests +test/actual +actual +coverage + +# npm node_modules npm-debug.log -actual -test/actual + +# misc +_gh_pages +benchmark +bower_components +vendor temp tmp TODO.md -vendor -.idea -benchmark -coverage diff --git a/.travis.yml b/.travis.yml index d6e658e..3932eaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,14 @@ sudo: false language: node_js node_js: - - "stable" - - "0.12" - - "0.10" + - '6' + - '5' + - '4' + - '0.12' + - '0.10' matrix: fast_finish: true allow_failures: - - node_js: "0.10" + - node_js: '4' + - node_js: '0.10' + - node_js: '0.12' diff --git a/.verb.md b/.verb.md index 9796e9a..062714a 100644 --- a/.verb.md +++ b/.verb.md @@ -1,102 +1,253 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} +You might also be interested in: -> {%= description %} +- [generate](https://github.com/generate/generate) +- [assemble](https://github.com/assemble/assemble) +- [verb](https://github.com/verbose/verb) -## CLI +## Quickstart -### Install +Install Update's CLI and an example [updater](#updaters) globally: -{%= include("install-global") %} +```sh +$ npm install --global update && updater-example +``` + +Initialize `update`: + +```sh +$ update init +``` + +Run update: ```sh $ update ``` -### tasks -### plugins -#### pipeline plugins -#### instance plugins -### middleware +## Overview -A middleware function takes `file` and `next`. +### What does Update do? -```js -function rename(file, next) { - file.path = 'foo/' + path.basename(file.path); - next(); -} +All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. + +You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: + +* create continuity and consistency across projects +* enforce conventions across all of your projects +* instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) +* reformat code to meet your standards +* convert a config file to a different format (json to yaml, yaml to json, etc) +* update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: + - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners + - remove deprecated fields from [project manifests](https://github.com/update/updater-package) + - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) +* after initializing a new project with a project generator, like [generate][] or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences + + +### Why should I use Update? + +- **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. +- **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. +- **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble][], [verb][], and [generate][], to name a few. +- **well tested**: with more than 1,250 unit tests + +### Examples + +Here are some random example commits after running `$ update`. + +**Project**/**Commit** | **Updaters used** +--- | --- +[generate-scaffold][generate-scaffold-commit] | `editorconfig`, `travis` +[updater-editorconfig][updater-editorconfig-commit] | `editorconfig`, `eslint`, `travis`, `license` +[expand-target][et] | `editorconfig`, `eslint`, `travis`, `package` + +### Features + +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters][getting-started] and [tasks](docs/tasks.md) +* **generators**: support for [generate][] generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][], or anything supported by [consolidate][]. +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp][], use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl][] files +* **streams**: full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin from the Base ecosystem can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! + + +## CLI + +### Installing update + +**Install update** + +To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): + +```sh +$ npm install --global update ``` +This adds the `update` command to your system path, allowing it to be run from anywhere. -**Example** -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i +### Installing updaters -```js -app.onStream(/lib\//, rename); +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/updateupdater), but if you're not familiar with how Update works, we recommend installing `updater-example`: + +```sh +$ npm install --global updater-example ``` +**Create "example.txt"** -## API +In the current working directory, create an empty file named `example.txt`. -### Install +**Run** -{%= include("install-npm", {save: true}) %} +As a habit, when using `update` make sure your work is committed, then run: -```js -var update = require('{%= name %}'); +```sh +$ update example ``` -## API -{%= apidocs("index.js") %} +This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example][] project for additional steps and guidance. + +## Tasks + +Update ships with the following built-in [tasks](docs/tasks.md). These will be externalized to an updater or [generate][] generator at some point. + +{%= apidocs("lib/updatefile.js") %} + +## Help menu + +```console +$ update help + + Usage: update [options] + + Command: updater or tasks to run + + Options: + + --config, -c Save a configuration value to the `update` object in package.json + --cwd Set or display the current working directory + --help, -h Display this help menu + --init, -i Prompts you to choose the updaters to automatically run (your "queue") + --add Add updaters to your queue + --remove Remove updaters from your queue + --run Force tasks to run regardless of command line flags used + --silent, -S Silence all tasks and updaters in the terminal + --show Display the value of + --version, -V Display the current version of update + --verbose, -v Display all verbose logging messages + + Examples: + + # run updater "foo" + $ update foo + + # run task "bar" from updater "foo" + $ update foo:bar + + # run multiple tasks from updater "foo" + $ update foo:bar,baz,qux + + # run a sub-generator from updater "foo" + $ update foo.abc -## Related projects -{%= related(verb.related.list) %} + # run task "xyz" from sub-generator "foo.abc" + $ update foo.abc:xyz -## Authoring + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying its default task. Example: `$ update foo:default` +``` + +## API ### Updaters -#### Tasks -#### Middleware -#### Plugins -> Updater plugins follow the same signature as gulp plugins +#### Discovering updaters -**Example** +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` +* Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team + +#### Discovering plugins + +Plugins from any applications built on [base][] should work with Update (and can be used in your updater): + +* [base][base-plugin]: find base plugins on npm using the `baseplugin` keyword +* [assemble][assemble-plugin]: find assemble plugins on npm using the `assembleplugin` keyword +* [generate][generate-plugin]: find generate plugins on npm using the `generateplugin` keyword +* [templates][templates-plugin]: find templates plugins on npm using the `templatesplugin` keyword +* [update][update-plugin]: find update plugins on npm using the `updateplugin` keyword +* [verb][verb-plugin]: find verb plugins on npm using the `verbplugin` keyword + +#### Authoring updaters + +Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. + +## Configuration + +Customize settings and default behavior using the `update` property in package.json. These values will override global defaults. ```js -function foo(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - // do stuff - file.contents = new Buffer(file.contents); - this.push(file); - cb(); - }); +{ + "update": { + "updaters": ["package", "license", "keywords"] + } } ``` -### Publish - -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in package.json -4. Tweet about your updater! +### Options +The following options may be defined in package.json. -## Running tests -{%= include("tests") %} +#### updaters -## Contributing -{%= include("contributing") %} +The updaters to run on the current project. -## Author -{%= include("author") %} +**Example** -## License -{%= copyright() %} -{%= license() %} +Run `updater-license` and `updater-package` on the current project: -*** +```js +{ + "update": { + "updaters": ["package", "license"] + } +} +``` -{%= include("footer") %} +## More information + +- See the [updaters maintained by the core team](https://github.com/update) +- Browse the [documentation](docs) +- Browse the [API documentation](docs/api) +- Learn about [updaters](docs/updaters.md) +- Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +- Learn more about [base][] +- Get [Sublime Text Snippets][st] for creating tasks and updaters + +## Release History +{%= increaseHeadings(changelog('CHANGELOG.md', { + changelogFooter: true, + stripHeading: true, + repo: repo +})) %} + +[Update]: https://github.com/update/update +[getting-started]: https://github.com/update/getting-started +[base-plugin]: https://www.npmjs.com/browse/keyword/baseplugin +[assemble-plugin]: https://www.npmjs.com/browse/keyword/assembleplugin +[generate-plugin]: https://www.npmjs.com/browse/keyword/generateplugin +[templates-plugin]: https://www.npmjs.com/browse/keyword/templatesplugin +[update-plugin]: https://www.npmjs.com/browse/keyword/updateplugin +[verb-plugin]: https://www.npmjs.com/browse/keyword/verbplugin +[st]: https://github.com/node-base/sublime-text-base-snippets + +[generate-scaffold-commit]: https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe +[updater-editorconfig-commit]: https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf +[et]: https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..170cc9c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# Release History + +## key + +Changelog entries are classified using the following labels from [keep-a-changelog][]: + +* `added`: for new features +* `changed`: for changes in existing functionality +* `deprecated`: for once-stable features removed in upcoming releases +* `removed`: for deprecated features removed in this release +* `fixed`: for any bug fixes + +Custom labels used in this changelog: + +* `dependencies`: bumps dependencies +* `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories. + +**Heads up!** + +Please [let us know](../../issues) if any of the following heading links are broken. Thanks! + +## [0.7.3] - 2016-07-21 + +**fixed** + +- ensure `app.cwd` in the current instance is the cwd defined by the user on the options or argv. + +## [0.7.0] - 2016-07-21 + +**added** + +- as of v0.7.0, we will begin using the [keep-a-changelog][] format for release history +- adds support user-defined templates +- adds support for `app.home()`, which resolves to `~/` or the user-defined `options.homedir`. This directory is used to determine the base directory for user-defined templates. +- adds support for [common-config](https://github.com/jonschlinkert/common-config). Exposed on the `app.common` object (e.g. `app.common.set()` etc) +- adds experimental support for a `home` updater. If an `updatefile.js` exists in the `~/update` directory (this will be customizable, but it's not yet), this file will be loaded and `.use()`d as a plugin before other updaters are loaded. You can use this to set options, add defaults, etc. But you can also run it explictly via commandline with the `update home` command. + +**fixed** + +- fixes `app.cwd` so that it's updated when `app.options.dest` (`--dest`) is set +- ensure args are parsed consistently + +## [0.6.0] + +- Swap out [base][] for [assemble-core][] (which uses Base via [templates][]). This allows updaters to seamlessly run generators from [generate][], [assemble][], or [verb][] (when a file needs to be created, or re-created for example) +- Adds [assemble-loader][] to support glob patterns in collection methods + +## [0.5.0] + +First stable release! + +[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog diff --git a/LICENSE b/LICENSE index 65f90ac..1e49edf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015, Jon Schlinkert. +Copyright (c) 2015-2016, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bf2d6b0..e129f2d 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,400 @@ -# update-next [![NPM version](https://badge.fury.io/js/update-next.svg)](http://badge.fury.io/js/update-next) +

-> Update + + + +

-## CLI +Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects. + +# update + +[![NPM version](https://img.shields.io/npm/v/update.svg?style=flat)](https://www.npmjs.com/package/update) [![NPM monthly downloads](https://img.shields.io/npm/dm/update.svg?style=flat)](https://npmjs.org/package/update) [![Build Status](https://img.shields.io/travis/update/update.svg?style=flat)](https://travis-ci.org/update/update) + +You might also be interested in: -### Install +* [generate](https://github.com/generate/generate) +* [assemble](https://github.com/assemble/assemble) +* [verb](https://github.com/verbose/verb) -Install globally with [npm](https://www.npmjs.com/) +## Quickstart + +Install Update's CLI and an example [updater](#updaters) globally: ```sh -$ npm i -g update-next +$ npm install --global update && updater-example ``` +Initialize `update`: + +```sh +$ update init +``` + +Run update: + ```sh $ update ``` -### tasks +## Overview -### plugins +### What does Update do? -#### pipeline plugins +All updating is accomplished using plugins called _updaters_, which are run by command line or API, and can be installed globally, locally, or created in a local `updatefile.js`. -#### instance plugins +You can create your own [updaters](docs/updaters.md) using Update's API, or install updaters using [npm](https://www.npmjs.com/), to do things like: -### middleware +* create continuity and consistency across projects +* enforce conventions across all of your projects +* instantly update an old or inherited project to your latest personal preferences (convert tabs to spaces, convert from `jshint` to `eslint` or the other way around, or any other detail) +* reformat code to meet your standards +* convert a config file to a different format (json to yaml, yaml to json, etc) +* update files that are typically excluded from build cycles, and are often forgotten about after they're created. For example: + - fix dates in copyrights, [licenses](https://github.com/update/updater-license) and banners + - remove deprecated fields from [project manifests](https://github.com/update/updater-package) + - update settings in [runtime config](https://github.com/update/updater-eslint) files, preferences in [dotfiles](https://github.com/update/updater-editorconfig) +* after initializing a new project with a project generator, like [generate](https://github.com/generate/generate) or Google's Yeoman, you can "normalize" all of the generated files to use your own preferences -A middleware function takes `file` and `next`. +### Why should I use Update? -```js -function rename(file, next) { - file.path = 'foo/' + path.basename(file.path); - next(); -} -``` +* **be more productive**: Update eliminates time spent on things that _can be automated_, but typically aren't since they either don't need to be done often, don't fit into the build cycle or a project's deliverables, or because they're usually updated manually. As code projects mature, time spent on these things tend to stay linear or increase as the size of a community grows. If you maintain more than a handful of projects, time spent on these things compounds with each new project under your stewardship. +* **your way, instantly**: updaters can be published to and installed from npm, but you can also easily create your own [personal updaters](docs/symlinking-updaters.md). Once your updaters are setup, just run `update init`, then projects under your maintenance will convert to the the conventions you prefer within milliseconds after running `update`. +* **plugin ecosystem**: any plugins that work with [Base applications](#discovering-plugins) will work also with Update. Which means you can use plugins (or generators) from [assemble](https://github.com/assemble/assemble), [verb](https://github.com/verbose/verb), and [generate](https://github.com/generate/generate), to name a few. +* **well tested**: with more than 1,250 unit tests -**Example** +### Examples -The `onStream` method is a custom [middleware](docs/middleware.md) handler that the `update` i +Here are some random example commits after running `$ update`. -```js -app.onStream(/lib\//, rename); +**Project**/**Commit** | **Updaters used** +--- | --- +[generate-scaffold](https://github.com/generate/generate-scaffold/commit/440d71f7293cb1f79445c0161440afbb266a2fbe) | `editorconfig`, `travis` +[updater-editorconfig](https://github.com/update/updater-editorconfig/commit/b7bd0aa616519440fa4a0d29d3aefac26787cbaf) | `editorconfig`, `eslint`, `travis`, `license` +[expand-target](https://github.com/jonschlinkert/expand-target/commit/48d70a0bc95d8eb3f7def615b7e231e8f93816e8) | `editorconfig`, `eslint`, `travis`, `package` + +### Features + +* **unparalleled flow control**: through the use of [updaters](docs/updaters.md), [sub-updaters](https://github.com/update/getting-started) and [tasks](docs/tasks.md) +* **generators**: support for [generate](https://github.com/generate/generate) generators. If your updater needs to create new files, there might be a [generator for that](https://www.npmjs.com/browse/keyword/generate-generator). Just use the generator the same way you would use an [updater](docs/updaters.md). +* **render templates**: use templates to create new files, or replace existing files +* **prompts**: It's easy to create custom prompts. Answers to prompts can be used as context for rendering templates, for settings options, determining file names, directory structure, and anything else that requires user feedback. +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](https://pugjs.org), or anything supported by [consolidate](https://github.com/visionmedia/consolidate.js). +* **data**: gather data from the user's environment to populate "hints" in user prompts or for rendering templates +* **fs**: in the spirit of [gulp](http://gulpjs.com), use `.src` and `.dest` to read and write globs of files. +* **vinyl**: files and templates are [vinyl](https://github.com/gulpjs/vinyl) files +* **streams**: full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin from the Base ecosystem can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on. +* much more! + +## CLI + +### Installing update + +**Install update** + +To use Update's CLI, `update` must first be installed globally with [npm](https://www.npmjs.com/): + +```sh +$ npm install --global update ``` -## API +This adds the `update` command to your system path, allowing it to be run from anywhere. -### Install +### Installing updaters -Install with [npm](https://www.npmjs.com/) +Updaters can be [found on npm](https://www.npmjs.com/browse/keyword/updateupdater), but if you're not familiar with how Update works, we recommend installing `updater-example`: ```sh -$ npm i update-next --save +$ npm install --global updater-example ``` -```js -var update = require('update-next'); +**Create "example.txt"** + +In the current working directory, create an empty file named `example.txt`. + +**Run** + +As a habit, when using `update` make sure your work is committed, then run: + +```sh +$ update example ``` -## API +This appends the string `foo` to the contents of `example.txt`. Visit the [updater-example](https://github.com/update/updater-example) project for additional steps and guidance. -### [Update](index.js#L40) +## Tasks -Create an `update` application. This is the main function exported by the update module. +Update ships with the following built-in [tasks](docs/tasks.md). These will be externalized to an updater or [generate](https://github.com/generate/generate) generator at some point. -**Params** +### [init](lib/updatefile.js#L29) -* `options` **{Object}** +Select the updaters to run every time `update` is run. Use `--add` to add additional updaters, and `--remove` to remove them. You can run this command whenever you want to update your preferences, like after installing new updaters. **Example** -```js -var Update = require('update'); -var update = new Update(); +```sh +$ update init ``` -## Related projects +### [list](lib/updatefile.js#L64) -* [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io) -* [boilerplate](https://www.npmjs.com/package/boilerplate): Tools and conventions for authoring and publishing boilerplates that can be generated by any build… [more](https://www.npmjs.com/package/boilerplate) | [homepage](http://boilerplates.io) -* [composer](https://www.npmjs.com/package/composer): API-first task runner with three methods: task, run and watch. | [homepage](https://github.com/jonschlinkert/composer) -* [generate](https://www.npmjs.com/package/generate): Project generator, for node.js. | [homepage](https://github.com/generate/generate) -* [scaffold](https://www.npmjs.com/package/scaffold): Conventions and API for creating scaffolds that can by used by any build system or… [more](https://www.npmjs.com/package/scaffold) | [homepage](https://github.com/jonschlinkert/scaffold) -* [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine.… [more](https://www.npmjs.com/package/templates) | [homepage](https://github.com/jonschlinkert/templates) -* [update](https://www.npmjs.com/package/update): Update the year in all files in a project using glob patterns. | [homepage](https://github.com/jonschlinkert/update) -* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://www.npmjs.com/package/verb) | [homepage](https://github.com/verbose/verb) +Display a list of currently installed updaters. + +**Example** + +```sh +$ update defaults:list +# aliased as +$ update list +``` + +### [help](lib/updatefile.js#L85) + +Display a help [menu](#help-menu) of available commands and flags. + +**Example** + +```sh +$ update defaults:help +# aliased as +$ update help +``` -## Authoring +### [show](lib/updatefile.js#L101) -### Create your own updater +Show the list of updaters that are registered to run on the current project. -#### tasks +**Example** -#### tasks +```sh +$ update defaults:show +# aliased as +$ update show +``` -#### plugins +### [help](lib/updatefile.js#L120) -> Updater plugins follow the same signature as gulp plugins +Default task for the built-in `defaults` generator. **Example** +```sh +$ update help +``` + +## Help menu + +```console +$ update help + + Usage: update [options] + + Command: updater or tasks to run + + Options: + + --config, -c Save a configuration value to the `update` object in package.json + --cwd Set or display the current working directory + --help, -h Display this help menu + --init, -i Prompts you to choose the updaters to automatically run (your "queue") + --add Add updaters to your queue + --remove Remove updaters from your queue + --run Force tasks to run regardless of command line flags used + --silent, -S Silence all tasks and updaters in the terminal + --show Display the value of + --version, -V Display the current version of update + --verbose, -v Display all verbose logging messages + + Examples: + + # run updater "foo" + $ update foo + + # run task "bar" from updater "foo" + $ update foo:bar + + # run multiple tasks from updater "foo" + $ update foo:bar,baz,qux + + # run a sub-generator from updater "foo" + $ update foo.abc + + # run task "xyz" from sub-generator "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying its default task. Example: `$ update foo:default` +``` + +## API + +### Updaters + +#### Discovering updaters + +* Find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/updateupdater) for packages with the keyword `updateupdater` +* Visit [Update's GitHub org](https://github.com/update) to see the updaters maintained by the core team + +#### Discovering plugins + +Plugins from any applications built on [base](https://github.com/node-base/base) should work with Update (and can be used in your updater): + +* [base](https://www.npmjs.com/browse/keyword/baseplugin): find base plugins on npm using the `baseplugin` keyword +* [assemble](https://www.npmjs.com/browse/keyword/assembleplugin): find assemble plugins on npm using the `assembleplugin` keyword +* [generate](https://www.npmjs.com/browse/keyword/generateplugin): find generate plugins on npm using the `generateplugin` keyword +* [templates](https://www.npmjs.com/browse/keyword/templatesplugin): find templates plugins on npm using the `templatesplugin` keyword +* [update](https://www.npmjs.com/browse/keyword/updateplugin): find update plugins on npm using the `updateplugin` keyword +* [verb](https://www.npmjs.com/browse/keyword/verbplugin): find verb plugins on npm using the `verbplugin` keyword + +#### Authoring updaters + +Visit the [updater documentation](docs/updaters.md) guide to learn how to use, author and publish updaters. + +## Configuration + +Customize settings and default behavior using the `update` property in package.json. These values will override global defaults. + ```js -function foo(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - // do stuff - file.contents = new Buffer(file.contents); - this.push(file); - cb(); - }); +{ + "update": { + "updaters": ["package", "license", "keywords"] + } } ``` -### Publish +### Options -1. Name your project following the convention: `updater-*` -2. Don't use dots in the name (e.g `.js`) -3. Make sure you add `updater` to the keywords in package.json -4. Tweet about your updater! +The following options may be defined in package.json. -## Running tests +#### updaters -Install dev dependencies: +The updaters to run on the current project. -```sh -$ npm i -d && npm test +**Example** + +Run `updater-license` and `updater-package` on the current project: + +```js +{ + "update": { + "updaters": ["package", "license"] + } +} ``` -## Contributing +## More information + +* See the [updaters maintained by the core team](https://github.com/update) +* Browse the [documentation](docs) +* Browse the [API documentation](docs/api) +* Learn about [updaters](docs/updaters.md) +* Learn about the [built-in updaters](docs/cli/built-in-updaters.md) +* Learn more about [base](https://github.com/node-base/base) +* Get [Sublime Text Snippets](https://github.com/node-base/sublime-text-base-snippets) for creating tasks and updaters + +## Release History + +### key + +Changelog entries are classified using the following labels from [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog): + +* `added`: for new features +* `changed`: for changes in existing functionality +* `deprecated`: for once-stable features removed in upcoming releases +* `removed`: for deprecated features removed in this release +* `fixed`: for any bug fixes + +Custom labels used in this changelog: -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/update-next/issues/new). +* `dependencies`: bumps dependencies +* `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories. + +**Heads up!** + +Please [let us know](../../issues) if any of the following heading links are broken. Thanks! + +### [0.7.3](https://github.com/update/update/compare/0.7.0...0.7.3) - 2016-07-21 + +**fixed** + +* ensure `app.cwd` in the current instance is the cwd defined by the user on the options or argv. + +### [0.7.0](https://github.com/update/update/compare/0.6.0...0.7.0) - 2016-07-21 + +**added** + +* as of v0.7.0, we will begin using the [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog) format for release history +* adds support user-defined templates +* adds support for `app.home()`, which resolves to `~/` or the user-defined `options.homedir`. This directory is used to determine the base directory for user-defined templates. +* adds support for [common-config](https://github.com/jonschlinkert/common-config). Exposed on the `app.common` object (e.g. `app.common.set()` etc) +* adds experimental support for a `home` updater. If an `updatefile.js` exists in the `~/update` directory (this will be customizable, but it's not yet), this file will be loaded and `.use()`d as a plugin before other updaters are loaded. You can use this to set options, add defaults, etc. But you can also run it explictly via commandline with the `update home` command. + +**fixed** + +* fixes `app.cwd` so that it's updated when `app.options.dest` (`--dest`) is set +* ensure args are parsed consistently + +### [0.6.0](https://github.com/update/update/compare/0.5.0...0.6.0) + +* Swap out [base](https://github.com/node-base/base) for [assemble-core](https://github.com/assemble/assemble-core) (which uses Base via [templates](https://github.com/jonschlinkert/templates)). This allows updaters to seamlessly run generators from [generate](https://github.com/generate/generate), [assemble](https://github.com/assemble/assemble), or [verb](https://github.com/verbose/verb) (when a file needs to be created, or re-created for example) +* Adds [assemble-loader](https://github.com/assemble/assemble-loader) to support glob patterns in collection methods + +### [0.5.0] + +First stable release! + +_(Changelog generated by [helper-changelog](https://github.com/helpers/helper-changelog))_ + +## About + +### Related projects + +* [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit") +* [base](https://www.npmjs.com/package/base): Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks | [homepage](https://github.com/node-base/base "Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks") +* [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.") +* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") + +### Community + +Are you using [Update](https://github.com/update/update) in your project? Have you published an [updater](https://github.com/update/update/blob/master/docs/updaters.md) and want to share your Update project with the world? + +Here are some suggestions! + +* If you get like Update and want to tweet about it, please use the hashtag `#updatejs` (not `@`) +* Show your love by starring [Update](https://github.com/update/update) and `update` +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/update) (please use the `updatejs` tag in questions) +* **Gitter** Discuss Update with us on [Gitter](https://gitter.im/update/update) +* If you publish an updater, thank you! To make your project as discoverable as possible, please add the keyword `updateupdater` to package.json. + +### Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). + +Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards. + +### Running tests + +Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: + +```sh +$ npm install && npm test +``` -## Author +### Author **Jon Schlinkert** -+ [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) +* [github/jonschlinkert](https://github.com/jonschlinkert) +* [twitter/jonschlinkert](https://twitter.com/jonschlinkert) -## License +### License -Copyright © 2015 Jon Schlinkert -Released under the MIT license. +Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). +Released under the [MIT License](LICENSE). *** -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on November 02, 2015._ \ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 23, 2018._ \ No newline at end of file diff --git a/bin/update.js b/bin/update.js index 7c643f7..e9fdc13 100755 --- a/bin/update.js +++ b/bin/update.js @@ -1,37 +1,65 @@ #!/usr/bin/env node -var path = require('path'); -var Runner = require('../lib/runner/runner')(); -var utils = require('../lib/utils'); -var argv = require('minimist')(process.argv.slice(2), { - alias: {verbose: 'v'} +process.on('exit', function() { + require('set-blocking')(true); }); -var cmd = utils.commands(argv); -var runner = new Runner(argv); +var os = require('os'); +var Update = require('..'); +var commands = require('../lib/commands'); +var utils = require('../lib/utils'); +var argv = utils.parseArgs(process.argv.slice(2)); -runner.base.option(argv); -runner.option(argv); +/** + * Listen for errors + */ -var task = cmd.list ? ['list', 'default'] : 'default'; +Update.on('update.preInit', function(app) { + app.on('error', function(err) { + console.log(err.stack); + process.exit(1); + }); +}); -runner.on('*', function(method, key, val) { - console.log(method + ':', key, val); +Update.on('update.postInit', function(app) { + commands(app); }); -if (argv.verbose) { - runner.on('register', function(key) { - utils.ok(utils.gray('registered'), 'updater', utils.cyan(key)); - }); -} +/** + * Init CLI + */ -runner.registerEach('update-*', {cwd: utils.gm}); +Update.cli(Update, argv, function(err, app) { + if (err) return console.log(err); -runner.base.task('run', function(cb) { - runner.run(cb); -}); + app.cli.process(argv, function(err) { + if (err) app.emit('error', err); -runner.base.build(task, function(err) { - if (err) return console.error(err); - utils.timestamp('done ' + utils.green(utils.successSymbol)); + var tasks = argv._.length ? argv._ : ['default']; + if (app.updatefile !== true || argv.run) { + tasks = Update.resolveTasks(app, argv); + + } else if (app.updatefile === true && app.pkg.get('update.run')) { + tasks = Update.resolveTasks(app, argv).concat(tasks); + } + + app.once('task', function() { + if (!app.base.enabled('silent')) { + app.log.success('running:', logRunning(app, tasks.join(', '))); + } + }); + + app.update(tasks, function(err) { + if (err) return console.log(err); + app.emit('done'); + process.exit(); + }); + }); }); + +function logRunning(app, str) { + if (os.platform() === 'win32') { + return app.log.bold(app.log.cyan(str)); + } + return app.log.bold(app.log.blue(str)); +} diff --git a/docs/api/plugins.md b/docs/api/plugins.md new file mode 100644 index 0000000..8834950 --- /dev/null +++ b/docs/api/plugins.md @@ -0,0 +1,65 @@ +# Plugins + +A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins](https://github.com/node-base/base-plugins) documentation for additional details. + +### .use + +The `.use` method is used for registering plugins that should be immediately invoked. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above +} + +app.use(plugin); +``` + +Once a plugin is invoked, it will not be called again. + +### .run + +If a plugin returns a function after it's invoked by `.use`, the function will be pushed onto an array allowing it to be called again by the `.run` method. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above + return plugin; +} + +app.use(plugin); +``` + +We can now run all plugins that were pushed onto the `.fns` array on any arbitrary object: + +```js +var obj = {}; +app.run(obj); +``` + +Additionally: + +* If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array. + +This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. + +## Updaters + +When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. + +## Related + +**API** + +* [updater](updater.md) +* [register](register.md) diff --git a/docs/api/register.md b/docs/api/register.md new file mode 100644 index 0000000..1c507ba --- /dev/null +++ b/docs/api/register.md @@ -0,0 +1,35 @@ +# Register + +Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. + +```js +app.register(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `fn` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// not invoked until called by `.update` +app.register('foo', function(app) { + // do updater stuff +}); + +app.update('foo', function(err) { + if (err) return console.log(err); +}); +``` + +## Related + +**API** + +* [updater](updater.md) +* [plugins](plugins.md) diff --git a/docs/api/updater.md b/docs/api/updater.md new file mode 100644 index 0000000..2ffed48 --- /dev/null +++ b/docs/api/updater.md @@ -0,0 +1,43 @@ +# Updater + +Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. + +```js +app.updater(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `updater` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// immediately invoked +app.updater('bar', function(app) { + // do updater stuff +}); + +app.update('bar', function(err) { + if (err) return console.log(err); +}); +``` + +## Related + +**CLI** + +* [commands](../cli/commands.md) + +**API** + +* [register](register.md) +* [plugins](plugins.md) + +**Docs** + +* [faq](../faq.md) diff --git a/docs/cli/built-in-updaters.md b/docs/cli/built-in-updaters.md new file mode 100644 index 0000000..6b40573 --- /dev/null +++ b/docs/cli/built-in-updaters.md @@ -0,0 +1,51 @@ +# Built in updaters + +Update only has a few built-in [updaters](docs/updaters.md) (these might be externalized at some point): + +* [init](#init): Choose the updaters to run by default each time `update` is run from the command line +* [list](#list): List all globally and locally installed updaters +* [show](#show): show the list of updaters that will run on the current project when the `update` command is given +* [new](#new): create a new `updatefile.js` in the current working directory +* [help](#help): show a help menu with all available commands + +## Usage + +### init + +Prompts you to choose one or more updaters to run by default each time `update` is run from the command line: + +```sh +$ update init +``` + +### list + +List all globally and locally installed updaters: + +```sh +$ update list +``` + +### show + +Show the list of updaters that will run on the current project when the `update` command is given: + +```sh +$ update show +``` + +### new + +Create a new `updatefile.js` in the current working directory: + +```sh +$ update new +``` + +### help + +Display a help menu with all available commands: + +```sh +$ update help +``` diff --git a/docs/cli/commands.md b/docs/cli/commands.md new file mode 100644 index 0000000..41df6d6 --- /dev/null +++ b/docs/cli/commands.md @@ -0,0 +1,17 @@ +# Command line flags + +Supported command line flags. + +## --run + +By default, when an `updatefile.js` exists in the current working directory, the `update` command will only run explicitly specified tasks or, if no tasks are explicitly defined, the `default` task in `updatefile.js`. + +The `--run` flag forces `update` to run stored tasks and the `default` task or explicitly specified tasks in `updatefile.js`. Stored tasks are executed first, in the order defined, then the `default` task or explicitly defined tasks. + +**Default**: `undefined` + +**Example** + +```sh +$ update --run +``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..edb8ea8 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,23 @@ +# Faq + +## How is the name written? + +Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase, no capital letters. + + + +## Aliases + +**What's an updater's alias, and what do they do?** + +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. + +An updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing an updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). + +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. + +## Related + +**Docs** + +* [features](features.md) diff --git a/docs/features.md b/docs/features.md new file mode 100644 index 0000000..60e9e14 --- /dev/null +++ b/docs/features.md @@ -0,0 +1,20 @@ +# Features + +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: + +* **unparalleled flow control**: through the use of [updaters](https://github.com/update/getting-started), [sub-updaters](https://github.com/update/getting-started) and [tasks](https://github.com/update/getting-started) +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) +* **any engine**: use any template engine to render templates, including [handlebars](http://www.handlebarsjs.com/), [lodash](https://lodash.com/), [swig](https://github.com/paularmstrong/swig) and [pug](http://jade-lang.com) +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want +* **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates +* **streams**: interact with the file system, with full support for [gulp](http://gulpjs.com) and [assemble](https://github.com/assemble/assemble) plugins +* **smart plugins**: Update is built on [base](https://github.com/node-base/base), so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on + +Visit the [getting started guide](https://github.com/update/getting-started) to learn more. + +## Related + +**Docs** + +* [faq](faq.md) diff --git a/docs/installing-the-cli.md b/docs/installing-the-cli.md new file mode 100644 index 0000000..7f384c8 --- /dev/null +++ b/docs/installing-the-cli.md @@ -0,0 +1,50 @@ +# Installing the cli + +To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: + +```sh +$ npm install --global update +``` + +This adds the `update` command to your system path, allowing it to be run from any directory. + +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to run any locally or globally installed updaters by their [aliases](tasks.md#alias-tasks) or full names. + +**Init** + +If it's your first time using update, run `update init` to set your global defaults. + +**CLI help** + +``` +Usage: update [options] + +Command: Updater or tasks to run + +Examples: + + # run the "foo" updater + $ update foo + + # run the "bar" task on updater "foo" + $ update foo:bar + + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux + + # run a sub-updater on updater "foo" + $ update foo.abc + + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` + +## Related + +**Docs** + +* [installing-updaters](installing-updaters.md) diff --git a/docs/installing-updaters.md b/docs/installing-updaters.md new file mode 100644 index 0000000..450bb6a --- /dev/null +++ b/docs/installing-updaters.md @@ -0,0 +1,11 @@ +# Installing updaters + +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages that have the keyword `update-updater`. + +TODO + +## Related + +**Docs** + +* [installing-the-cli](installing-the-cli.md) diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000..3009a36 Binary files /dev/null and b/docs/logo.png differ diff --git a/docs/nested-updaters.md b/docs/nested-updaters.md new file mode 100644 index 0000000..ff87484 --- /dev/null +++ b/docs/nested-updaters.md @@ -0,0 +1,49 @@ +# Nested updaters + +Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. + +TBC... + +## TODO + +* [ ] explain how nested updaters work +* [ ] command line syntax +* [ ] API syntax + +## Pre-requisites + +* [plugins](api/plugins.md) +* [updaters](updaters.md) + +## Sub-updaters + +As with [plugins](api/plugins.md), updaters may be nested: _any updater can register other updaters, and any updater can be registered by other updaters._ We refer to nested updaters as **sub-updaters**. + +**Example** + +```js +app.register('foo', function(foo) { + // do updater stuff + this.register('bar', function(bar) { + // do updater stuff + this.register('baz', function(baz) { + // do updater stuff + this.task('default', function(cb) { + console.log(baz.namespace); + cb(); + }); + }); + }); +}); +``` + +## Run nested updaters + +Use dot-notation to get the updater you wish to run: + +```js +app.update('foo.bar.baz', function(err) { + if (err) return console.log(err); + +}); +``` diff --git a/docs/symlinking-updaters.md b/docs/symlinking-updaters.md new file mode 100644 index 0000000..3e4347c --- /dev/null +++ b/docs/symlinking-updaters.md @@ -0,0 +1,75 @@ +# Symlinking updaters + +While developing [updaters](updaters.md), you might find it useful to symlink them to global `node_modules` so that Update's CLI will find them and run them, as if they had been installed from npm using `npm install --global`. + +The following example shows you to do this. + +## Example + +**1. Create an updater project** + +Create a new project named `updater-aaa`. You can expedite this using [generate](https://github.com/generate/generate) or Google's Yeoman or however you prefer. + +**2. Add `index.js`** + +In `index.js`, add the following code: + +```js +// -- index.js -- +module.exports = function(app) { + app.task('default', function(cb) { + console.log('updater', app.name, '> task', this.name); + cb(); + }); +}; +``` + +* `app.name` will display the name of the updater being run +* `this.name` will display the name of the task being run + +_(Also make sure the `index.js` is listed in the `main` property in package.json, so that node's `require()` system finds the file)_ + +**3. Symlink** + +Next, we need to symlink the module to global `node_modules`, so that `updater-aaa` is discoverable by Update's CLI. + +From the root of the `updater-aaa` project, run the following command: + +```sh +$ npm link +``` + +**4. Run** + +To test that `updater-aaa` was symlinked properly, run the following command: + +```sh +$ update aaa +``` + +You should see something like the following in the terminal + +```sh +updater updater-aaa > task default +``` + +If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. + +**Next steps** + +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. + +Then run: + +```sh +update aaa bbb ccc +``` + +## Related + +**Docs** + +* [tasks](tasks.md) +* [updatefile](updatefile.md) +* [installing-updaters](installing-updaters.md) +* [updaters](updaters.md) diff --git a/docs/tasks.md b/docs/tasks.md new file mode 100644 index 0000000..9ca80ba --- /dev/null +++ b/docs/tasks.md @@ -0,0 +1,180 @@ +# Tasks + +Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. + +- [Creating tasks](#creating-tasks) +- [Running tasks](#running-tasks) + * [Command line](#command-line) + * [Task API](#task-api) + + [.task](#task) + + [.build](#build) + + [.update](#update) + * [Task composition](#task-composition) + + [Task dependencies](#task-dependencies) + + [Alias tasks](#alias-tasks) + * [default task](#default-task) + +## Creating tasks + +Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. + +```js +app.task('foo', function(cb) { + // since tasks are asynchronous, you must call the callback when the task is complete + cb(); +}); +``` + +## Running tasks + +Tasks can be run by command line or API. + +### Command line + +Pass the names of the tasks to run after the `update` command. + +**Examples** + +Run task `foo`: + +```sh +update foo +``` + +Run tasks `foo`, `bar` and `baz`: + +```sh +update foo bar baz +``` + +**Conflict resolution** + +You might notice that [updaters](updaters.md) can also be run from the command line using the same syntax. Update can usually determine whether you meant to call tasks or updaters. Visit the [running updaters](updaters.md#running-updaters) documentation for more information. + +### Task API + +#### .task + +Create a task: + +```js +app.task(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the task to register +* `fn` **{Function}**: asynchronous callback function, or es6 generator function + +**Example** + +```js +app.task('default', function(cb) { + // do task stuff (be sure to call the callback) + cb(); +}); +``` + +**Stream or callback** + +When using update's file system API (`.src`/`.dest` etc), you can optionally return a stream instead of calling a callback. Either a callback must be called, or a stream must be returned, otherwise update has no way of knowing when a task is complete. + +#### .build + +Run one or more tasks. + +**Params** + +* `names` **{String|Array|Function}**: names of one or more tasks to run, or callback function if you only want to run the [default task](#default-task) +* `callback`: callback function, invoked after all tasks have finished executing. The callback function exposes `err` as the only argument, with any errors that occurred during the execution of any tasks. + +**Example** + +```js +app.task('foo', function(cb) { + // do task stuff + cb(); +}); + +app.task('bar', function(cb) { + // do task stuff + cb(); +}); + +app.build(['foo', 'bar'], function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +#### .update + +The `.update` method may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. + +_To ensure that only tasks are run, use the `.build` method._ + +See the [updaters documentation](updaters.md) for more details. + +### Task composition + +#### Task dependencies + +When a task has "dependencies", this means that one or more other tasks need to finish before the task is executed. + +Dependencies can be passed as the second argument to the `.task` method. + +**Example** + +In the following example, task `foo` has dependencies `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz'], function(cb) { + // do task stuff + cb(); +}); +``` + +Task `foo` will not execute until tasks `bar` and `baz` have completed. + +#### Alias tasks + +An "alias" task is a task with one or more dependencies and _no callback_. + +**Example** + +In this example, task `foo` is an alias for tasks `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz']); +``` + +In this example, task `foo` is an alias for task `baz` + +```js +app.task('foo', ['baz']); +``` + +### default task + +The `default` task is run automatically when a callback is passed as the only argument: + +```js +app.task('default', function(cb) { + // do task stuff + cb(); +}); + +// no need to specify "default", but you can if you want +app.build(function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +## Related + +**Docs** + +* [Running updaters](updaters.md#running-updaters) +* [updaters](updaters.md) +* [updatefile](updatefile.md) diff --git a/docs/updatefile.md b/docs/updatefile.md new file mode 100644 index 0000000..6e878ee --- /dev/null +++ b/docs/updatefile.md @@ -0,0 +1,62 @@ +# Updatefile + +Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. + +**If `updatefile.js` exists** + +Update's CLI attempts to: + +* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. +* Load the configuration from `updatefile.js` using node.js `require()` system +* Register it as the ["default" updater](updaters.md#default-updater) +* Execute any tasks or updaters you've specified for it to run. +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run all of the specified tasks and updaters. + +**If `updatefile.js` does not exist** + +Update's CLI attempts to: + +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally and globally installed modules with the name `updater-*`. + +## Creating an updatefile.js + +An `updatefile.js` may contain any custom JavaScript code, but must export a function that takes an instance of Update (`app`): + +**Example** + +```js +// -- updatefile.js -- +module.exports = function(app) { + // custom code here +}; +``` + +Inside this function, you can define [tasks](tasks.md), additional [updaters](updaters.md), or any other custom JavaScript code necessary for your updater: + +```js +module.exports = function(app) { + // register a task + app.task('default', function(cb) { + // do task stuff + cb(); + }); + + // register an updater + app.register('foo', function() { + + }); + + // register another updater + app.register('bar', function() { + + }); +}; +``` + +## Related + +**Docs** + +* [installing-updaters](installing-updaters.md) +* [updaters](updaters.md) +* [tasks](tasks.md) diff --git a/docs/updaters.md b/docs/updaters.md new file mode 100644 index 0000000..46c8de2 --- /dev/null +++ b/docs/updaters.md @@ -0,0 +1,246 @@ +# Updaters + +This document describes how to create, register and run updaters. + +- [TODO](#todo) +- [What is an updater?](#what-is-an-updater) +- [Creating updaters](#creating-updaters) +- [Registering updaters](#registering-updaters) + * [.register](#register) + * [.updater](#updater) +- [Running updaters](#running-updaters) +- [Resolving updaters](#resolving-updaters) + * [Tasks and updaters](#tasks-and-updaters) + * [Naming tips](#naming-tips) + * [Order of precendence](#order-of-precendence) +- [Discovering updaters](#discovering-updaters) +- [Default updater](#default-updater) + +## TODO + +* [ ] document updater args +* [ ] explain how the `base` instance works +* [ ] document `env` + +## What is an updater? + +Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. + +The primary difference between "updaters" and "plugins" is how they're registered, but there are a few other minor differences: + +| | **Plugin** | **Updater** | +| --- | --- | --- | +| Registered with | [.use](api/plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | +| Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | +| Run using | [.run](api/plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | + +## Creating updaters + +An updater function takes an instance of `Update` as the first argument. + +**Example** + +```js +function updater(app) { + // do updater stuff +} +``` + +## Registering updaters + +Updaters may be registered using either of the following methods: + +* `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) +* `.updater`: if the plugin needs to be invoked immediately when registered + +### .register + +Register an updater function with the given `name` using the `.register` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is run with the `.update` method. + console.log('foo is being run'); +} + +// register as an updater with the `.register` method +app.register('foo', updater); + +// run the `foo` updater with the `.update` method +app.update('foo', function(err) { + if (err) return console.log(err); +}); +//=> "foo is being run" +``` + +### .updater + +Register an updater function with the given `name` using the `.updater` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is registered + console.log('foo is being registered'); +} + +// register as an updater using `.updater` +app.updater('foo', updater); +//=> "foo is being registered" +``` + +**Should I use `.updater` or `.register`?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to invoke the function when the updater is registered. + +## Running updaters + +Updaters and their tasks can be run by command line or API. + +**Command line** + +To run globally or locally installed `updater-foo`, or an updater named `foo` in `updatefile.js`, run: + +```sh +$ update foo +``` + +**API** + +```js +var update = require('update'); +var app = update(); + +function fn() { + // do updater stuff +} + +// the `.register` method does not invoke the updater +app.register('foo', fn); + +// the `.updater` method invokes the updater immediately +app.updater('bar', fn); + +// run updaters foo and bar in series (both updaters will be invoked) +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); +}); +``` + +## Resolving updaters + +Updaters can be published to npm and installed globally or locally. But there is no requirement that updaters must be published. You can also create custom updaters and register using the [.register](#register) or [.updater](#updater) methods. + +This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. + +### Tasks and updaters + +1. When both a task and an updater have the same name _on the same instance_, Update will always try to run the task first (this is unlikely to happen unless you intend for it to - there are [reasons to do this](#naming-tips)) + +### Naming tips + +Since the [.build](tasks.md#build) method only runs tasks, you can use this to your advantage by aliasing sub-generators with tasks. + +**Don't do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.update` will run updater `foo` + app.update('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Or this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + app.task('foo', function(cb) { + app.update('foo', cb); + }); + + // `.build` will run task `foo`, which runs updater `foo` + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +### Order of precendence + +When the command line is used, Update's CLI resolves updaters in the following order: + +1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater +2. built-in updaters: attempts to match given names to Update's [built-in updaters](cli/built-in-updaters.md) +3. locally installed updaters +4. globally installed updaters + +## Discovering updaters + +todo + +## Default updater + +If an updater is registered with the name `default` it will receive special treatment from Update and Update's CLI. More specifically, when Update's CLI looks for updaters or tasks to run, it will search for them on the `default` updater first. + +There is a catch... + +**Registering the "default" updater** + +_The only way to register a `default` updater is by creating an [updatefile.js](updatefile.md) in the current working directory._ + +When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. + +## Related + +**Docs** + +* [tasks](tasks.md) +* [updatefile](updatefile.md) +* [installing-updaters](installing-updaters.md) +* [symlinking-updaters](symlinking-updaters.md) diff --git a/examples.js b/examples.js deleted file mode 100644 index 8c5de7b..0000000 --- a/examples.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -var path = require('path'); -var del = require('rimraf'); -var through = require('through2'); -var update = require('./'); -var app = update(); - -// app.config.get = function () { - -// }; -// console.log(app) - - -// app.config({ -// plugins: function (obj) { -// for (var key in obj) { -// var name = path.basename(key, path.extname(key)); -// var fn = require(path.resolve(key)); -// app.plugin(name, obj[key], fn); -// } -// } -// }); - - -// app.config('plugins', function (obj) { -// for (var key in obj) { -// var name = path.basename(key, path.extname(key)); -// var fn = require(path.resolve(key)); -// app.plugin(name, obj[key], fn); -// } -// }); - -// app.config.process(); - -// app.plugin('a', require('./lib/pipeline/a')()); -// app.plugin('b', require('./lib/pipeline/b')); -// app.plugin('c', require('./lib/pipeline/c')()); -// app.plugin(/foo/, require('./lib/pipeline/d')()); - - -// app.on('error', function(err) { -// console.log('Error in plugin:', err.plugin, err.message) -// }); - -// app.task('default', function (cb) { -// app.src('LICENSE') -// .pipe(through.obj(function (file, enc, next) { -// if (file.isNull()) return next(); -// next(null, file); -// })) -// .pipe(app.pipeline()) -// .pipe(app.dest('actual')) -// .on('finish', cb); -// }); - -// app.build('default', function(err) { -// if (err) return console.log(err); -// }); diff --git a/gulpfile.js b/gulpfile.js index 7d0c54a..fa32719 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,25 +3,33 @@ var gulp = require('gulp'); var mocha = require('gulp-mocha'); var istanbul = require('gulp-istanbul'); +var unused = require('gulp-unused'); var eslint = require('gulp-eslint'); -var lint = ['index.js', 'lib/*.js', 'test/*.js']; +var lib = ['index.js', 'lib/*.js', 'lib/commands/*.js', 'bin/*.js']; -gulp.task('coverage', function () { - return gulp.src(lint) +gulp.task('coverage', function() { + return gulp.src(lib) .pipe(istanbul()) .pipe(istanbul.hookRequire()); }); -gulp.task('mocha', ['coverage'], function () { +gulp.task('test', ['coverage'], function() { return gulp.src('test/*.js') .pipe(mocha({reporter: 'spec'})) .pipe(istanbul.writeReports()); }); -gulp.task('eslint', function () { - return gulp.src(lint) +gulp.task('lint', function() { + return gulp.src(['*.js', 'test/*.js', 'lib/**/*.js']) .pipe(eslint()) + .pipe(eslint.format()); }); -gulp.task('default', ['mocha', 'eslint']); +gulp.task('unused', function() { + var keys = Object.keys(require('./lib/utils.js')); + return gulp.src(lib) + .pipe(unused({keys: keys})); +}); + +gulp.task('default', ['test', 'lint']); diff --git a/index.js b/index.js index a45ade5..2681299 100644 --- a/index.js +++ b/index.js @@ -1,39 +1,27 @@ /*! * update * - * Copyright (c) 2015, Jon Schlinkert. + * Copyright (c) 2016, Jon Schlinkert. * Licensed under the MIT License. */ 'use strict'; -/** - * module dependencies - */ - +var fs = require('fs'); +var os = require('os'); var path = require('path'); -var minimist = require('minimist'); -var expand = require('expand-args'); -var cli = require('base-cli'); -var store = require('base-store'); -var pipeline = require('base-pipeline'); -var loader = require('assemble-loader'); var Base = require('assemble-core'); -var ask = require('assemble-ask'); - -var config = require('./lib/config'); -var locals = require('./lib/locals'); var utils = require('./lib/utils'); +var cli = require('./lib/cli'); /** - * Create an `update` application. This is the main function exported - * by the update module. + * Create a update application with `options`. * * ```js - * var Update = require('update'); - * var update = new Update(); + * var update = require('update'); + * var app = update(); * ``` - * @param {Object} `options` + * @param {Object} `options` Settings to initialize with. * @api public */ @@ -42,154 +30,227 @@ function Update(options) { return new Update(options); } Base.call(this, options); - this.name = this.options.name || 'update'; - this.isUpdate = true; + this.is('update'); this.initUpdate(this); + this.initDefaults(); } /** - * Inherit assemble-core + * Inherit `Base` */ Base.extend(Update); /** - * Initialize Updater defaults + * Initialize defaults, emit events before and after */ -Update.prototype.initUpdate = function(base) { - this.set('updaters', {}); +Update.prototype.initUpdate = function() { + Update.emit('update.preInit', this); + Update.plugins(this); + Update.emit('update.postInit', this); +}; + +/** + * Initialize `Update` defaults + */ - // custom middleware handlers - this.handler('onStream'); - this.handler('preWrite'); - this.handler('postWrite'); +Update.prototype.initDefaults = function() { + this.define('generators', this.generators); + this.define('updater', this.generator); + this.updaters = this.generators; + var self = this; - // parse command line arguments - var argv = expand(minimist(process.argv.slice(2)), { - alias: {v: 'verbose'} + this.define('home', function() { + var args = [].slice.call(arguments); + var home = path.resolve(self.options.homedir || os.homedir()); + return path.resolve.apply(path, [home].concat(args)); }); - this.option('argv', argv); + this.option('help', {configname: 'updater', appname: 'update'}); + this.define('update', this.generate); + this.define('getUpdater', function() { + return this.getGenerator.apply(this, arguments); + }); - // expose `argv` on the instance - this.mixin('argv', function(prop) { - var args = [].slice.call(arguments); - args.unshift(argv); - return utils.get.apply(null, args); + this.option('toAlias', function(name) { + return name.replace(/^updater?-(.*)$/, '$1'); }); - // load the package.json for the updater - this.data(utils.pkg.sync(this.options.path)); - config(this); + function isUpdater(name) { + return /^(updater|generate)?-/.test(name); + } - this.use(utils.runtimes({ - displayName: function (key) { - return this.name === key ? key : (this.name + ':' + key); + // create `app.globals` store + Object.defineProperty(this, 'globals', { + configurable: true, + get: function() { + return new utils.Store('generate-globals', { + cwd: utils.resolveDir('~/') + }); } - })) + }); - this.use(locals('update')) - .use(store()) - .use(pipeline()) - .use(loader()) - .use(ask()) - .use(cli()) + Object.defineProperty(this, 'common', { + configurable: true, + get: function() { + return utils.common; + } + }); - .use(utils.defaults()) - .use(utils.opts()) + this.onLoad(/(^|[\\\/])templates[\\\/]/, function(view, next) { + var userDefined = self.home('templates', view.basename); + if (utils.exists(userDefined)) { + view.contents = fs.readFileSync(userDefined); + view.homePath = userDefined; + view.isUserDefined = true; + } + if (/^templates[\\\/]/.test(view.relative)) { + view.path = path.join(self.cwd, view.basename); + } + utils.stripPrefixes(view); + utils.parser.parse(view, next); + }); - var data = utils.get(this.cache.data, 'update'); - data = utils.extend({}, data, argv); + this.option('lookup', function(name) { + var patterns = []; + if (!isUpdater(name)) { + patterns.push(`updater-${name}`); + } + return patterns; + }); - this.config.process(data); + this.on('unresolved', function(search, app) { + if (!isUpdater(search.name)) return; + var resolved = utils.resolve.file(search.name) || utils.resolve.file(search.name, {cwd: utils.gm}); + if (resolved) { + search.app = app.generator(search.name, require(resolved.path)); + } + }); - this.engine(['md', 'tmpl'], require('engine-base')); - this.onLoad(/\.(md|tmpl)$/, function (view, next) { - utils.matter.parse(view, next); + this.on('option', function(key, val) { + if (key === 'dest') { + self.base.cwd = val; + self.cwd = val; + } }); -}; -Update.prototype.cwd = function(dir) { - var cwd = dir || process.cwd(); - return function() { - var args = [].slice.call(arguments); - args.unshift(cwd); - return path.resolve.apply(null, args); - }; -}; + this.on('ask', function(val, key, question, answers) { + val = val || question.default; + if (typeof val === 'undefined') { + question.default = self.common.get(key); + } + }); -Update.prototype.log = function() { - if (this.enabled('verbose')) { - console.log.apply(console, arguments); - } + this.on('task:starting', function(event, task) { + if (event && event.app) { + event.app.cwd = self.base.options.dest || self.base.cwd || event.app.cwd; + } + if (task && task.app) { + task.app.cwd = self.base.options.dest || self.base.cwd || task.app.cwd; + } + }); }; -Update.prototype.flag = function(key) { - return this.get('argv.' + key); -}; +/** + * Expose plugins on the constructor to allow other `base` + * apps to use the plugins before instantiating. + */ -Update.prototype.cmd = function(key) { - return utils.commands(this.argv)[key] || false; +Update.prototype.configfile = function(cwd) { + return utils.configfile(cwd); }; -Update.prototype.extendFile = function(file, config, opts) { - var parsed = utils.tryParse(file.content); - var obj = utils.extend({}, parsed, config); - var res = {}; - if (opts && opts.sort === true) { - var keys = Object.keys(obj).sort(); - var len = keys.length, i = -1; - while (++i < len) { - var key = keys[i]; - res[key] = obj[key]; - } - } else { - res = obj; +/** + * Get the list of updaters to run + */ + +Update.prototype.getUpdaters = function(names, options) { + if (utils.isObject(names)) { + options = names; + names = []; + } + options = options || {}; + var updaters = this.option('updaters'); + this.addUpdaters(names, options); + if (utils.isEmpty(updaters)) { + updaters = this.pkg.get('update.updaters'); + } + if (utils.isEmpty(updaters)) { + updaters = this.globals.get('updaters'); } - file.content = JSON.stringify(res, null, 2); - if (opts.newline) file.content += '\n'; + if (options.remove) { + updaters = utils.remove(updaters, utils.toArray(options.remove)); + } + if (options.add) { + updaters = utils.union([], updaters, utils.toArray(options.add)); + } + return updaters; }; /** - * Register updater `name` with the given `update` - * instance. - * - * @param {String} `name` - * @param {Object} `update` Instance of update - * @return {Object} Returns the instance for chaining + * Get the list of updaters to run */ -Update.prototype.updater = function(name, app) { - if (arguments.length === 1 && typeof name === 'string') { - return this.updaters[name]; +Update.prototype.addUpdaters = function(names, options) { + options = options || {}; + if (typeof names === 'string') { + names = utils.toArray(names); } + if (options.config) { + this.pkg.union('update.updaters', names); + } + if (options.global) { + this.globals.union('updaters', names); + } +}; - app.use(utils.runtimes({ - displayName: function(key) { - return name + ':' + key; - } - })); +/** + * Expose plugins on the constructor to allow other `base` + * apps to use the plugins before instantiating. + */ - this.emit('updater', name, app); - this.updaters[name] = app; - return app; +Update.plugins = function(app) { + app.use(utils.logger()); + app.use(utils.generators()); + app.use(utils.store('update')); + app.use(utils.runtimes()); + app.use(utils.questions()); + app.use(utils.loader()); + app.use(utils.config()); + app.use(utils.cli()); }; /** - * Expose `Update` + * Get the updaters or tasks to run from user config */ -module.exports = Update; +Update.resolveTasks = function(app, argv) { + var tasks = utils.arrayify(argv._); + if (tasks.length && utils.contains(['help', 'list', 'new', 'default'], tasks)) { + app.enable('silent'); + return tasks; + } + + if (tasks.length && !utils.contains(['help', 'list', 'new', 'default'], tasks)) { + return tasks; + } + + tasks = app.getUpdaters(argv.add, argv); + if (!tasks || !tasks.length) { + return ['init']; + } + return tasks; +}; /** - * Expose `utils` + * Expose static `cli` method */ -module.exports.utils = utils; +Update.cli = cli; /** - * Expose package.json metadata + * Expose `update` */ -module.exports.pkg = require('./package'); +module.exports = Update; diff --git a/lib/_updaters/copyright.js b/lib/_updaters/copyright.js deleted file mode 100644 index 5768c9f..0000000 --- a/lib/_updaters/copyright.js +++ /dev/null @@ -1,9 +0,0 @@ - -var fs = require('fs'); -var pkg = require('load-pkg')(); -var copyright = require('update-copyright'); - -var str = fs.readFileSync('LICENSE', 'utf8'); -var updated = copyright(str, pkg); - -console.log(updated); diff --git a/lib/_updaters/license.js b/lib/_updaters/license.js deleted file mode 100644 index 56ce211..0000000 --- a/lib/_updaters/license.js +++ /dev/null @@ -1,46 +0,0 @@ - -var fs = require('fs'); -var del = require('delete'); -var writeFile = require('write'); -var green = require('ansi-green'); -var success = require('success-symbol'); -var cwd = require('cwd'); - -var banner = 'The MIT License (MIT)\n\n'; - -function addBanner(str) { - if (!/^The MIT License \(MIT\)/i.test(str)) { - str = banner + str; - } - return str; -} - -function update(filepath, cb) { - var fp = cwd(filepath); - - fs.readFile(fp, 'utf8', function(err, str) { - if (err) return cb(err); - - - del(filepath, function(err) { - if (err) return cb(err); - - writeFile('LICENSE', str, function(err) { - if (err) return cb(err); - - return cb(null, 'updated'); - }); - }); - }); -} - -update('LICENSE', function(err, res) { - if (err) { - return console.error(err); - } - var msg = ' LICENSE is already up to date.'; - if (res === 'updated') { - msg = ' updated LICENSE'; - } - console.log(green(success), msg); -}); diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..502793b --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,168 @@ +'use strict'; + +process.ORIGINAL_CWD = process.cwd(); + +var os = require('os'); +var path = require('path'); +var find = require('find-pkg'); +var log = require('log-utils'); +var utils = require('./utils'); +var argv = utils.parseArgs(process.argv.slice(2)); + +module.exports = function(Update, config, cb) { + if (typeof cb !== 'function') { + throw new TypeError('expected a callback function'); + } + if (!config || typeof config !== 'object') { + throw new TypeError('expected config to be an object'); + } + + var opts = utils.extend({}, config, argv); + + function logger() { + if (opts.silent) return; + console.log.apply(console, arguments); + } + + /** + * Resolve `package.json` filepath and use it for `cwd` + */ + + var pkgPath = find.sync(process.cwd()); + var cwd = pkgPath ? path.dirname(pkgPath) : process.cwd(); + + /** + * Set `cwd` + */ + + if (cwd !== process.cwd()) { + logger(log.timestamp, 'using cwd', log.yellow('~' + cwd)); + process.chdir(cwd); + } + + /** + * Get the Base ctor and instance to use + */ + + var base = resolveBase(); + if (!(base instanceof Update)) { + base = new Update(opts); + } + + /** + * Require the config file (instance or function) + */ + + base.set('cache.argv', opts); + + /** + * Check for user `~/update/updatefile.js` + */ + + var homeUpdatefile = path.resolve(os.homedir(), 'update/updatefile.js'); + + /** + * Check for config file + */ + + var defaults = path.resolve(__dirname, 'updatefile.js'); + var updatefile = path.resolve(cwd, 'updatefile.js'); + var fn = require(defaults); + + /** + * Invoke `app` + */ + + if (utils.exists(updatefile)) { + logger(log.timestamp, 'using file', log.green('~' + updatefile)); + var val = require(updatefile); + if (typeof val === 'function') { + function updater() { + if (!utils.isValid(this, 'default-updater')) return; + this.use(fn); + this.use(val); + } + base.register('default', updater); + } else if (val && val.isApp) { + base = val; + } + base.updatefile = true; + } else { + logger(log.timestamp, 'using file', log.green('~' + defaults)); + base.register('default', fn); + } + + /** + * Register updater in user home + */ + + if (utils.exists(homeUpdatefile)) { + base.register('home', require(homeUpdatefile)); + } + + /** + * Handle errors + */ + + if (!base) { + handleError(base, new Error('cannot run config file: ' + updatefile)); + } + if (typeof base !== 'function' && Object.keys(base).length === 0) { + handleError(base, new Error('expected a function or instance of Base to be exported')); + } + + /** + * Merge `argv` onto options + */ + + base.option(argv); + + /** + * Setup listeners + */ + + base.on('error', function(err) { + logger(err.stack); + }); + + base.on('build', function(event, build) { + if (!build || build.isSilent) return; + var prefix = event === 'finished' ? log.success + ' ' : ''; + logger(log.timestamp, event, build.key, prefix + log.red(build.time)); + }); + + base.on('task', function(event, task) { + if (!task || task.isSilent) return; + logger(log.timestamp, event, task.key, log.red(task.time)); + }); + + /** + * Resolve the "app" to use + */ + + function resolveBase() { + var file = {path: path.resolve(cwd, 'node_modules/update'), name: 'update'}; + if (utils.exists(file.path)) { + var Update = require(file.path); + var update = new Update(opts); + + if (typeof update._name === 'undefined') { + update.is('update'); + } + + Update.plugins(update); + return update; + } + } + + function handleError(app, err) { + if (app && app.hasListeners && app.hasListeners('error')) { + app.emit('error', err); + } else { + console.error(err.stack); + process.exit(1); + } + } + + cb(null, base); +}; diff --git a/lib/commands.js b/lib/commands.js new file mode 100644 index 0000000..d53bfef --- /dev/null +++ b/lib/commands.js @@ -0,0 +1,11 @@ +'use strict'; + +var commands = require('./commands/'); + +module.exports = function(app, options) { + for (var key in commands) { + if (commands.hasOwnProperty(key)) { + app.cli.map(key, commands[key](app, options)); + } + } +}; diff --git a/lib/commands/add.js b/lib/commands/add.js new file mode 100644 index 0000000..624901e --- /dev/null +++ b/lib/commands/add.js @@ -0,0 +1,44 @@ +'use strict'; + +var utils = require('../utils'); + +/** + * Remove names from the list of locally or globally stored updaters. + * + * ```sh + * # remove updater `foo` + * $ update -r foo + * # or + * $ update -rc foo + * # sugar for + * $ update --remove --config foo + * # remove globally stored updaters + * $ update -rg foo + * # sugar for + * $ update --remove --global foo + * ``` + * @name tasks + * @api public + * @cli public + */ + +module.exports = function(app, options) { + return function(names, key, config, next) { + var updaters = []; + + if (typeof names === 'string') { + names = utils.toArray(names); + } + + if (!config.config) { + updaters = app.globals.get('updaters') || []; + app.globals.set('updaters', updaters.concat(names)); + + } else { + updaters = app.pkg.get('update.updaters') || []; + app.pkg.union('update.updaters', updaters.concat(names)); + app.pkg.save(); + } + next(); + }; +}; diff --git a/lib/middleware/index.js b/lib/commands/index.js similarity index 100% rename from lib/middleware/index.js rename to lib/commands/index.js diff --git a/lib/commands/remove.js b/lib/commands/remove.js new file mode 100644 index 0000000..392a219 --- /dev/null +++ b/lib/commands/remove.js @@ -0,0 +1,50 @@ +'use strict'; + +var utils = require('../utils'); + +/** + * Remove names from the list of locally or globally stored updaters. + * + * ```sh + * # remove updater `foo` + * $ update -r foo + * # or + * $ update -rc foo + * # sugar for + * $ update --remove --config foo + * # remove globally stored updaters + * $ update -rg foo + * # sugar for + * $ update --remove --global foo + * ``` + * @name tasks + * @api public + * @cli public + */ + +module.exports = function(app, options) { + return function(names, key, config, next) { + if (typeof names === 'string') { + names = utils.toArray(names); + } + + var updaters = names.map(function(name) { + return 'updaters.' + name; + }); + + if (!config.config) { + app.globals.del(updaters); + + } else { + var list = app.pkg.get('update.updaters'); + app.pkg.del('update.updaters'); + + var rest = utils.remove(list, names); + if (rest.length) { + app.pkg.set('update.updaters', rest); + app.pkg.save(); + } + } + next(); + }; +}; diff --git a/lib/commands/silent.js b/lib/commands/silent.js new file mode 100644 index 0000000..3bd0d44 --- /dev/null +++ b/lib/commands/silent.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + return function(val, key, config, next) { + app.enable('silent'); + next(); + }; +}; diff --git a/lib/commands/version.js b/lib/commands/version.js new file mode 100644 index 0000000..abc843d --- /dev/null +++ b/lib/commands/version.js @@ -0,0 +1,10 @@ +'use strict'; + +var pkg = require('../../package'); + +module.exports = function(app) { + return function(val, key, config, next) { + console.log(app.log.cyan('Update v' + pkg.version)); + process.exit(); + }; +}; diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index c829c9b..0000000 --- a/lib/config.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - - -module.exports = function(app) { - if (!app.isUpdate) return; - - var config = require('base-config'); - app.use(config()); - - app.config - .map('addViews') - .map('addView') - .map('helpers') - .map('asyncHelpers') - .map('plugins', function(val) { - app.visit('plugin', val); - }) - .map('data', function(val) { - app.visit('data', val); - }) - .map('collections', function(val) { - app.visit('create', val); - }) - .map('reflinks', function(val) { - app.data({reflinks: val}); - }) - .map('related', function(val) { - app.data({related: val}); - }); -}; diff --git a/lib/context.js b/lib/context.js deleted file mode 100644 index f50064c..0000000 --- a/lib/context.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -var utils = require('./utils'); - -/** - * Create an instance of `Settings`. - * - * @class `Settings` - * @api public - */ - -function Settings() { - this.settings = []; -} - -/** - * Add a setting to merge. - * - * ```js - * setting - * .addSettings({a: 'b', c: 'd'}) - * .addSettings({a: 'z'}) - * .merge(); - * - * //=> {a: 'z', c: 'd'} - * ``` - * - * @param {Object} `object` The object to merge into the setting - * @api public - */ - -Settings.prototype.addSettings = function(obj) { - if (Array.isArray(obj) || arguments.length > 1) { - var args = [].concat.apply([], arguments); - args.forEach(function(arg) { - this.settings.push(arg); - }.bind(this)); - } else { - this.settings.push(obj); - } - return this; -}; - -/** - * Calculate the setting, optionally passing a callback `fn` for sorting. - * _(Note that sorting must be done on levels, not on the setting names)_. - * - * ```js - * app.calculate(['a', 'b'], function(a, b) { - * return app.lvl[a] - app.lvl[a]; - * }); - * ``` - * - * @param {String|Array} `keys` Key, or array of keys for setting levels to include. - * @param {Function} `fn` Sort function for determining the order of merging. - * @api public - */ - -Settings.prototype.merge = function(obj) { - if (obj) this.addSettings.apply(this, arguments); - var setting = {}; - this.settings.forEach(function(ctx) { - utils.merge(setting, ctx); - }); - return setting; -}; - -/** - * Export `Settings` - * - * @type {Object} - */ - -module.exports = Settings; diff --git a/lib/list.js b/lib/list.js new file mode 100644 index 0000000..ac2d96b --- /dev/null +++ b/lib/list.js @@ -0,0 +1,33 @@ +'use strict'; + +var path = require('path'); +var strip = require('strip-color'); +var through = require('through2'); +var table = require('text-table'); + +module.exports = function(app) { + function bold(str) { + return app.log.underline(app.log.bold(str)); + } + + var list = [[bold('version'), bold('name'), bold('alias')]]; + return through.obj(function(file, enc, next) { + var pkgPath = path.resolve(file.path, 'package.json'); + var pkg = require(pkgPath); + list.push([app.log.gray(pkg.version), file.basename, app.log.cyan(file.alias)]); + next(); + }, function(cb) { + console.log(); + console.log(table(list, { + stringLength: function(str) { + return strip(str).length; + } + })); + + console.log(); + console.log(app.log.magenta(list.length + ' updaters installed')); + console.log(); + cb(); + }); +}; + diff --git a/lib/locals.js b/lib/locals.js deleted file mode 100644 index 4d0097b..0000000 --- a/lib/locals.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var set = require('set-value'); -var utils = require('./utils'); - -module.exports = function(name) { - name = name || utils.project(process.cwd()); - - return function(app) { - app.define('locals', new Locals(name, this)); - }; -}; - -function Locals(name, app) { - this.cache = get(app, ['cache.data', name]) || {}; -} - -Locals.prototype.get = function(key) { - return get(this.cache, key); -}; - -Locals.prototype.set = function(key, value) { - set(this.cache, key, value); - return this; -}; diff --git a/lib/middleware/json.js b/lib/middleware/json.js deleted file mode 100644 index 9ce518f..0000000 --- a/lib/middleware/json.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var sortArrays = require('sort-object-arrays'); - -module.exports = function(app, base, env) { - base.onLoad(/\.js(on|hintrc)$/, function(file, next) { - file.json = JSON.parse(file.content); - next(); - }); - - base.preWrite(/\.js(on|hintrc)$/, function(file, next) { - file.json = sortArrays(file.json); - file.content = JSON.stringify(file.json, null, 2); - file.content += '\n'; - next(); - }); -}; diff --git a/lib/runner/argv.js b/lib/runner/argv.js deleted file mode 100644 index 95dd750..0000000 --- a/lib/runner/argv.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('argv', function(argv, commands, fn) { - var args = {}; - args.argv = argv; - args.commands = []; - args.updaters = {}; - - args.flags = utils.expandArgs(utils.omit(argv, ['_', 'files'])); - args.flagskeys = Object.keys(args.flags); - - var files = argv.files ? utils.pick(argv, 'files') : null; - if (files) args.flags.files = files; - - var arr = argv._; - var len = arr.length, i = -1; - - while (++i < len) { - var key = arr[i]; - - if (/\W/.test(key)) { - var obj = utils.expand(key); - - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - var val = obj[prop]; - utils.union(args, 'updaters.' + prop, val); - } - } - continue; - } - - if (utils.contains(commands, key)) { - args.commands.push(key); - continue; - } - // fn(key, args); - var updaters = this.base.updaters; - if (key in updaters) { - utils.union(args, 'updaters.' + key, 'default'); - - } else if (key !== 'base') { - utils.union(args, 'updaters.base', key); - } - } - return args; - }); - }; -}; - diff --git a/lib/runner/decorate.js b/lib/runner/decorate.js deleted file mode 100644 index e62a2f4..0000000 --- a/lib/runner/decorate.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('decorate', function(name, app, options) { - app.option('name', name) - .option('fullname', options.fullname || name) - .option('path', options.path || ''); - - app.create('templates', { - cwd: path.resolve(options.path, 'templates'), - renameKey: function(key) { - return path.basename(key); - } - }); - - var base = this.base; - - app.define('getFile', function(name) { - var view = base.files.getView.apply(base.files, arguments); - if (!view) { - view = app.templates.getView.apply(app.templates, arguments); - } - if (!view) return null; - view.basename = view.basename.replace(/^_/, '.'); - view.basename = view.basename.replace(/^$/, ''); - return view; - }); - - base.define('getFile', app.getFile); - base.files.getFile = base.files.getView.bind(base.files); - return this; - }); - }; -}; diff --git a/lib/runner/env.js b/lib/runner/env.js deleted file mode 100644 index 4d431b7..0000000 --- a/lib/runner/env.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -var path = require('path'); -var define = require('define-property'); -var extend = require('extend-shallow'); -var get = require('get-value'); -var set = require('set-value'); - -function Env(options) { - this.options = options || {}; - define(this, 'cache', {}); -} - -Env.prototype.set = function(key, value) { - set(this, key, value); - return this; -}; - -Env.prototype.get = function(key) { - return get(this, key); -}; - -Object.defineProperty(Env.prototype, 'cwd', { - set: function(dir) { - this.cache.cwd = dir; - }, - get: function() { - return this.cache.cwd || process.cwd(); - } -}); - -Object.defineProperty(Env.prototype, 'pkg', { - set: function() { - throw new Error('env.pkg is a getter and cannot be set directly.'); - }, - get: function() { - if (!this.cache.pkg) { - this.cache.pkg = require(path.resolve(this.cwd, 'package.json')); - } - return this.cache.pkg; - } -}); - -/** - * Expose `Env` - */ - -module.exports = function(options) { - return function(app) { - var opts = extend({}, this.options, options); - app.define('env', new Env(opts)); - }; -}; diff --git a/lib/runner/list.js b/lib/runner/list.js deleted file mode 100644 index 15122ee..0000000 --- a/lib/runner/list.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('list', function(cb) { - var questions = utils.questions(this.base.options); - var choices = utils.list(this.base.updaters); - if (!choices.length) { - console.log(utils.cyan(' No updater tasks found.')); - return cb(null, { - updaters: {} - }); - } - - var question = { - updaters: { - message: 'pick an updater to run', - type: 'checkbox', - choices: choices - } - }; - - questions.ask(question, function(err, answers) { - if (err) return cb(err); - var args = { - updaters: {} - }; - answers.updaters.forEach(function(answer) { - var segs = answer.split(':'); - if (segs.length === 1) return; - utils.union(args.updaters, segs[0], (segs[1] || 'default').split(',')); - }); - return cb(null, args); - }); - }); - }; -}; diff --git a/lib/runner/listen.js b/lib/runner/listen.js deleted file mode 100644 index dc48f2b..0000000 --- a/lib/runner/listen.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -module.exports = function(options) { - return function(app) { - this.define('_listen', function() { - if (this.base.disabled('verbose')) return; - var store = ['store.set', 'store.has', 'store.get', 'store.del']; - var methods = ['set', 'has', 'get', 'del', 'option', 'data']; - - var names = store.concat(methods); - var len = names.length, i = -1; - var multi = this; - - while (++i < len) { - var method = names[i]; - var prop = method.split('.'); - - if (prop.length === 2) { - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, method)); - this.base[prop[0]].on(prop[1], multi.emit.bind(multi, '*', method)); - } else { - this.base.on(method, function(key, val) { - multi.emit(method, key, val); - multi.emit('*', method, key, val); - }); - } - } - }); - }; -}; diff --git a/lib/runner/run.js b/lib/runner/run.js deleted file mode 100644 index 864451b..0000000 --- a/lib/runner/run.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(options) { - return function(app) { - - this.define('getApp', function(name) { - return name !== 'base' - ? this.base.updater(name) - : this.base; - }); - - this.define('run', function(args, cb) { - if (typeof args === 'function') { - cb = args; - args = null; - } - - if (!args) { - var commands = this.options.commands || this.commands; - args = this.argv(this.base.get('argv'), commands); - } - - if (args.commands && args.commands.length > 1) { - var cmd = '"' + args.commands.join(', ') + '"'; - return cb(new Error('Error: only one root level command may be given: ' + cmd)); - } - - this.base.cli.process(args.flags); - var self = this; - - utils.async.eachOf(args.updaters, function(tasks, name, next) { - var app = self.getApp(name); - - tasks = tasks.filter(Boolean); - if (!tasks.length) return next(); - - self.emit('task', name, tasks); - app.build(tasks, next); - }, cb); - - return this; - }); - }; -}; diff --git a/lib/runner/runner.js b/lib/runner/runner.js deleted file mode 100644 index 2ae1405..0000000 --- a/lib/runner/runner.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -var path = require('path'); -var option = require('base-options'); -var store = require('base-store'); -var Base = require('base-methods'); -var fns = require('../middleware'); -var Updater = require('./updater'); -var tasks = require('../tasks'); -var utils = require('../utils'); -var decorate = require('./decorate'); -var listen = require('./listen'); -var args = require('./argv'); -var list = require('./list'); -var run = require('./run'); -var Update = require('../..'); - -module.exports = function(namespace, config) { - function Runner(argv, options) { - if (!(this instanceof Runner)) { - return new Runner(argv, options); - } - - Base.call(this); - this.use(option()); - this.use(store()); - this.use(decorate()); - this.use(listen()); - this.use(args()); - this.use(list()); - this.use(run()); - - this.options = options || {}; - this.commands = ['set', 'get', 'del', 'store', 'init', 'option', 'data', 'list']; - - this.base = new Update() - .on('error', console.error) - .set('argv', argv) - - // register middleware - for (var fn in fns) { - fns[fn](this.base, this.base, this); - } - - // register tasks - for (var key in tasks) { - this.base.task(key, tasks[key](this.base, this.base, this)); - } - - this._listen(); - } - - Base.extend(Runner); - - Runner.prototype.updater = function(name) { - return this.base.updater(name); - }; - - Runner.prototype.build = function() { - this.base.build.apply(this.base, arguments); - return this; - }; - - Runner.prototype.register = function(name, options, updater) { - if (arguments.length === 2) { - updater = options; - options = {}; - } - - var Ctor = options.Update || Update; - var app = new Ctor(this.base.options); - this.decorate(name, app, options); - - updater.call(app, app, this.base, this); - this.base.updater(name, app); - - this.emit('register', name, app); - return this; - }; - - Runner.prototype.registerEach = function(patterns, options) { - utils.matchFiles(patterns, options).forEach(function(fp) { - var filepath = path.resolve(fp, 'updatefile.js'); - var updater = require(filepath); - - // get the full project name ('updater-foo') - var fullname = utils.project(fp); - // get the updater name ('foo') - var name = utils.renameFn(fullname, options); - var opts = {}; - - // get the constructor to use (node_modules or our 'Update') - opts.Update = utils.resolveModule(fp); - opts.fullname = fullname; - opts.path = fp; - - this.register(name, opts, updater); - }.bind(this)); - return this; - }; - - /** - * Expose `Runner` - */ - - return Runner; -}; diff --git a/lib/runner/updater.js b/lib/runner/updater.js deleted file mode 100644 index 222dcfa..0000000 --- a/lib/runner/updater.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; - -var path = require('path'); -var set = require('set-value'); -var define = require('define-property'); -var use = require('use'); - -/** - * Create an instance of `Updater`, optionally passing - * a default object to initialize with. - * - * ```js - * var app = new Updater({ - * path: 'foo.html' - * }); - * ``` - * @param {Object} `app` - * @api public - */ - -function Updater(name, config, fn) { - if (!(this instanceof Updater)) { - return new Updater(config); - } - - if (typeof config === 'function') { - fn = config; - config = {}; - } - - this.isUpdater = true; - define(this, 'cache', {}); - config = config || {}; - config.fn = fn; - - for (var key in config) { - if (!(key in this)) { - this.set(key, config[key]); - } - } - use(this); -} - -/** - * Set `key` on the instance with the given `value`. - * - * @param {String} `key` - * @param {Object} `value` - * @return {Object} Returns the instance for chaining - */ - -Updater.prototype.set = function(key, value) { - set(this, key, value); - return this; -}; - -/** - * Custom `inspect` method. - */ - -// Updater.prototype.inspect = function() { -// var name = this.name || 'Updater'; -// var inspect = []; - -// if (this.alias) { -// inspect.push('"' + this.alias + '"'); -// } -// return '<' + name + ' ' + inspect.join(' ') + '>'; -// }; - -/** - * Get the `cwd` (current working directory) for the updater. - */ - -define(Updater.prototype, 'cwd', { - set: function(dir) { - this.cache.cwd = dir; - }, - get: function() { - return this.cache.cwd || (this.cache.cwd = process.cwd()); - } -}); - -/** - * Get the `dirname` for the updater. - */ - -define(Updater.prototype, 'dirname', { - set: function(dir) { - this.path = path.join(dir, path.basename(this.path)); - }, - get: function() { - return path.dirname(this.path); - } -}); - -/** - * Get the `basename` for the updater. - */ - -define(Updater.prototype, 'basename', { - set: function(basename) { - this.path = path.join(path.dirname(this.path), basename); - }, - get: function() { - return path.basename(this.path); - } -}); - -/** - * Get the `filename` for the updater. - */ - -define(Updater.prototype, 'filename', { - set: function(filename) { - this.path = path.join(path.dirname(this.path), filename + this.extname); - }, - get: function() { - return path.basename(this.path, this.extname); - } -}); - -/** - * Expose `Updater` - */ - -module.exports = Updater; diff --git a/lib/tasks/default.js b/lib/tasks/default.js deleted file mode 100644 index 54bcf12..0000000 --- a/lib/tasks/default.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return ['del', 'files', 'run', 'dest']; -}; diff --git a/lib/tasks/del.js b/lib/tasks/del.js deleted file mode 100644 index acd270d..0000000 --- a/lib/tasks/del.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var path = require('path'); -var async = require('async'); -var rimraf = require('rimraf'); - -var list = ['.npmignore', '.jshintrc']; - -module.exports = function(app, base, env) { - var files = base.option('delete') || list; - - return function(cb) { - async.each(files, function(fp, next) { - rimraf(path.resolve(process.cwd(), fp), next); - }, cb); - }; -}; diff --git a/lib/tasks/dest.js b/lib/tasks/dest.js deleted file mode 100644 index 7422459..0000000 --- a/lib/tasks/dest.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app, base, env) { - var plugins = base.get('argv.plugins'); - - function handle(stage) { - return utils.through.obj(function(file, enc, next) { - if (file.isNull()) return next(); - app.handle(stage, file, next); - }); - } - - return function(cb) { - app.toStream('files') - .on('error', cb) - .pipe(handle('onStream')) - .pipe(app.pipeline(plugins)) - .pipe(handle('preWrite')) - .pipe(app.dest('.')) - .pipe(utils.exhaust(handle('postWrite'))) - .on('error', cb) - .on('end', cb); - }; -}; diff --git a/lib/tasks/files.js b/lib/tasks/files.js deleted file mode 100644 index 4c958ac..0000000 --- a/lib/tasks/files.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var path = require('path'); - -module.exports = function(app, base, env) { - base.create('files', { - renameKey: function(key) { - return path.basename(key); - } - }); - - var glob = base.get('argv.files'); - if (glob) { - glob = glob.split(','); - } else { - glob = ['*', 'lib/*', 'bin/*']; - } - - return function(cb) { - base.files(glob, {dot: true, ignore: ['.DS_Store']}); - base.emit('loaded', base.files); - cb(); - } -}; diff --git a/lib/tasks/jshint.js b/lib/tasks/jshint.js deleted file mode 100644 index 8dafe52..0000000 --- a/lib/tasks/jshint.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var through = require('through2'); -var jshint = require('gulp-jshint'); -var stylish = require('jshint-stylish'); - -module.exports = function(app, base, env) { - return function() { - return base.toStream('files') - .pipe(through.obj(function(file, enc, cb) { - if (/\.js$/.test(file.path)) { - this.push(file); - } - cb(); - })) - .pipe(jshint()) - .pipe(jshint.reporter(stylish)); - }; -}; diff --git a/lib/tasks/lint.js b/lib/tasks/lint.js deleted file mode 100644 index 5e04d25..0000000 --- a/lib/tasks/lint.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var through = require('through2'); -var eslint = require('gulp-eslint'); - -module.exports = function(app, base, env) { - return function() { - return base.toStream('files') - .pipe(through.obj(function(file, enc, cb) { - if (/\.js$/.test(file.path)) { - this.push(file); - } - cb(); - })) - .pipe(eslint()) - .pipe(eslint.format()); - }; -}; diff --git a/lib/tasks/list.js b/lib/tasks/list.js deleted file mode 100644 index 92ffe1d..0000000 --- a/lib/tasks/list.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return function(cb) { - env.list(function(err, args) { - if (err) return cb(err); - env.run(args, cb); - }); - }; -}; diff --git a/lib/tasks/noop.js b/lib/tasks/noop.js deleted file mode 100644 index 0386d7e..0000000 --- a/lib/tasks/noop.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = function(app, base, env) { - return function(cb) { - return cb(); - }; -}; diff --git a/lib/tasks/rename.js b/lib/tasks/rename.js deleted file mode 100644 index ad419c1..0000000 --- a/lib/tasks/rename.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -var path = require('path'); -var rimraf = require('rimraf'); -var through = require('through2'); - -var mapping = { - 'LICENSE': 'LICENSE-MIT', - 'readme.md': 'README.md' -}; - -module.exports = function(app, base, env) { - var config = base.option('rename') || mapping; - - app.task('undo', function() { - return base.toStream('files') - .pipe(rename(config, {invert: true})); - }); - - return function() { - return base.toStream('files') - .pipe(rename(config)); - }; -}; - -function rename(mapping, options) { - options = options || {}; - if (options.invert === true) { - mapping = invert(mapping); - } - return through.obj(function(file, enc, next) { - if (file.isNull()) return next(null, file); - var fp = file.path; - function del(err) { - if (err) return next(err); - next(null, file); - } - for (var key in mapping) { - if (isMatch(file, mapping[key])) { - file.path = path.resolve(file.base, key); - rimraf(fp, del); - return; - } - } - next(null, file); - }); -} - -function isMatch(file, src) { - file.basename = path.basename(file.path); - if (src instanceof RegExp) { - return src.test(file.basename) || src.test(file.path); - } - if (typeof src === 'string') { - return src === file.path || src === file.basename; - } -} - -function invert(obj) { - var res = {}; - for (var key in obj) { - res[obj[key]] = key; - } - return res; -} diff --git a/lib/tasks/tree.js b/lib/tasks/tree.js deleted file mode 100644 index 06e6cc8..0000000 --- a/lib/tasks/tree.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -var utils = require('../utils'); - -module.exports = function(app, base, env) { - return function(cb) { - console.log(utils.tree(base.updaters)); - cb(); - }; -}; diff --git a/lib/updatefile.js b/lib/updatefile.js new file mode 100644 index 0000000..f12e8ec --- /dev/null +++ b/lib/updatefile.js @@ -0,0 +1,167 @@ +'use strict'; + +var path = require('path'); +var isValid = require('is-valid-app'); +var choose = require('gulp-choose-files'); +var through = require('through2'); +var utils = require('./utils'); +var list = require('./list'); +var Update = require('..'); +var argv = require('yargs-parser')(process.argv.slice(2), utils.opts); + +module.exports = function(app, base) { + if (!isValid(app, 'update-builtins')) return; + var gm = path.resolve.bind(path, require('global-modules')); + var cwd = path.resolve.bind(path, app.cwd); + + /** + * Select the updaters to run every time `update` is run. Use `--add` to + * add additional updaters, and `--remove` to remove them. You can run this command + * whenever you want to update your preferences, like after installing new updaters. + * + * ```sh + * $ update init + * ``` + * @name init + * @api public + */ + + app.task('init', { silent: true }, function() { + var list = Update.resolveTasks(app, argv); + var updaters = []; + + console.log(); + console.log(' Current updaters:', app.log.cyan(list.join(', '))); + console.log(); + + return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) + .pipe(through.obj(function(file, enc, next) { + file.basename = app.toAlias(file.basename); + next(null, file); + })) + .pipe(choose({message: 'Choose the updaters to run with the `update` command:'})) + .pipe(through.obj(function(file, enc, next) { + updaters.push(file.basename); + next(); + }, function(next) { + save(app, updaters); + next(); + })); + }); + + /** + * Display a list of currently installed updaters. + * + * ```sh + * $ update defaults:list + * # aliased as + * $ update list + * ``` + * @name list + * @api public + */ + + app.task('list', { silent: true }, function() { + return app.src([gm('updater-*'), cwd('node_modules/updater-*')]) + .pipe(through.obj(function(file, enc, next) { + file.alias = app.toAlias(file.basename); + next(null, file); + })) + .pipe(list(app)); + }); + + /** + * Display a help [menu](#help-menu) of available commands and flags. + * + * ```sh + * $ update defaults:help + * # aliased as + * $ update help + * ``` + * @name help + * @api public + */ + + app.task('help', { silent: true }, function(cb) { + base.cli.process({ help: true }, cb); + }); + + /** + * Show the list of updaters that are registered to run on the current project. + * + * ```sh + * $ update defaults:show + * # aliased as + * $ update show + * ``` + * @name show + * @api public + */ + + app.task('show', { silent: true }, function(cb) { + argv._ = []; + var list = Update.resolveTasks(app, argv); + console.log(); + console.log(' queued updaters:', `\n · ` + list.join('\n · ')); + console.log(); + cb(); + }); + + /** + * Default task for the built-in `defaults` generator. + * + * ```sh + * $ update help + * ``` + * @name help + * @api public + */ + + app.task('default', { silent: true }, ['help']); +}; + +/** + * Save answers to `init` prompts + */ + +function save(app, list) { + if (!list.length) { + console.log(' no updaters were saved.'); + return; + } + + if (app.options.c || app.options.config) { + app.pkg.set('update.updaters', list); + } else { + app.globals.set('updaters', list); + } + + var suffix = list.length > 1 ? 'updaters are' : 'updater is'; + var gray = app.log.gray; + var cyan = app.log.cyan; + var bold = app.log.bold; + var command = 'update'; + + var msg = gray('\n ---') + + '\n' + + `\n ${app.log.green(app.log.check)} Done, your default ${suffix}:` + + '\n' + + cyan(`\n · ` + list.join('\n · ')) + + '\n' + + '\n' + + gray(' ---') + + '\n' + + '\n' + + bold(' Cheetsheet:') + + '\n' + + '\n' + + (` $ ${command} --init`) + gray(' initialize update (start over)\n') + + (` $ ${command} --remove `) + gray(' remove updaters from your queue\n') + + (` $ ${command} --add `) + gray(' add updaters to your queue\n') + + (` $ ${command} show`) + gray(' show your queued updaters\n') + + (` $ ${command} list`) + gray(' list all installed updaters\n') + + console.log(msg); + console.log(); +} + diff --git a/lib/utils.js b/lib/utils.js index c288293..d4e9b3c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,358 +1,163 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var pkg = require(path.resolve(__dirname, '../package')); - -/** - * Lazily required module dependencies - */ - var utils = require('lazy-cache')(require); var fn = require; -require = utils; - -require('for-own'); -require('async'); -require('expand-args'); -require('load-pkg', 'pkg'); -require('expand-object', 'expand'); -require('global-modules', 'gm'); -require('look-up', 'lookup'); -require('object.omit', 'omit'); -require('object.pick', 'pick'); -require('micromatch', 'mm'); -require('map-config', 'config'); -require('matched', 'glob'); -require('composer-runtimes', 'runtimes'); -require('parser-front-matter', 'matter'); -require('question-cache', 'questions'); -require('stream-exhaust', 'exhaust'); -require('extend-shallow', 'extend'); -require('resolve-dir', 'resolve'); -require('project-name', 'project'); -require('union-value', 'union'); -require('set-value', 'set'); -require('get-value', 'get'); -require('through2', 'through'); - -require('success-symbol'); -require('ansi-yellow', 'yellow'); -require('ansi-green', 'green'); -require('ansi-gray', 'gray'); -require('ansi-cyan', 'cyan'); -require('ansi-red', 'red'); -require('time-stamp', 'stamp'); - -require = fn; - - -utils.timestamp = function(msg) { - var time = '[' + utils.gray(utils.stamp('HH:mm:ss', new Date())) + ']'; - return console.log(time, msg); -}; - - -function Status(status) { - status = status || {}; - this.err = status.err || null; - this.code = status.code || null; - this.name = status.name || ''; - this.msg = status.msg || ''; -} - -utils.ok = function() { - var args = utils.toArray(arguments) || []; - args.unshift(' ' + utils.green(utils.successSymbol)); - console.log.apply(console, args); -}; -utils.success = function() { - var args = utils.toArray(arguments) || []; - args[0] = utils.green(args[0] || ''); - console.log.apply(console, args); -}; -utils.error = function() { - var args = utils.toArray(arguments); - args.unshift(utils.red('Error:')); - console.error.apply(console, args); -}; +require = utils; // eslint-disable-line /** - * CLI utils + * Utils */ -utils.commands = function(argv) { - argv._ = argv._ || []; - var commands = {}; - - argv._.forEach(function(key) { - commands[key] = true; - }); - return commands; -}; - -utils.identity = function(val) { - return val; -}; - -utils.arrayify = function(val) { - return Array.isArray(val) ? val : [val]; -}; - -utils.toArgv = function(args) { - var argv = args.flags; - argv._ = args.commands; - return argv; -}; - -utils.toArray = function(val) { - if (Array.isArray(val)) return val; - if (val && val.length) { - return [].slice.call(val); +require('assemble-loader', 'loader'); +require('arr-union', 'union'); +require('base-cli-process', 'cli'); +require('base-config-process', 'config'); +require('base-generators', 'generators'); +require('base-questions', 'questions'); +require('base-runtimes', 'runtimes'); +require('base-store', 'store'); +require('data-store', 'Store'); +require('common-config', 'common'); +require('extend-shallow', 'extend'); +require('fs-exists-sync', 'exists'); +require('isobject', 'isObject'); +require('is-valid-app', 'isValid'); +require('global-modules', 'gm'); +require('log-utils', 'log'); +require('resolve-dir'); +require('resolve-file', 'resolve'); +require('parser-front-matter', 'parser'); +require('yargs-parser', 'parse'); +require = fn; // eslint-disable-line + +utils.stripPrefixes = function(file) { + file.stem = file.stem.replace(/^_/, '.'); + file.stem = file.stem.replace(/^\$/, ''); +}; + +/** + * argv options + */ + +utils.opts = { + boolean: ['diff'], + alias: { + add: 'a', + config: 'c', + configfile: 'f', + diff: 'diffOnly', + global: 'g', + help: 'h', + init: 'i', + silent: 'S', + verbose: 'v', + version: 'V', + remove: 'r' } }; -utils.contains = function(arr, key) { - return arr.indexOf(key) > -1; -}; - -utils.npm = function(name) { - return utils.tryRequire(name) || utils.tryRequire(path.resolve(name)); -}; - -utils.exists = function(fp) { - return fs.existsSync(fp); -}; - -/** - * Create a global path for the given value - */ - -utils.toGlobalPath = function(fp) { - return '@/' + path.basename(fp, path.extname(fp)); -}; - -/** - * Create a global path for the given value - */ - -utils.findGlobal = function(pattern) { - return utils.lookup(pattern, { cwd: utils.gm }); -}; - -/** - * Get the resolved path to an "updatefile.js" - */ - -utils.updatefile = function(dir) { - return path.join(dir, 'updatefile.js'); -}; - -/** - * Rename a filepath to the "nickname" of the project. - * - * ```js - * renameFn('updater-foo'); - * //=> 'foo' - * ``` - */ - -utils.renameFn = function(filename, options) { - if (options && typeof options.renameFn === 'function') { - return options.renameFn(filename); +utils.parseArgs = function(argv) { + var obj = utils.parse(argv, utils.opts); + if (obj.init) { + obj._.push('init'); + delete obj.init; } - return filename.slice(filename.indexOf('-') + 1); -}; - -/** - * Return a glob of file paths - */ - -utils.matchFiles = function(pattern, options) { - options = options || {}; - var isMatch = utils.mm.matcher(pattern); - var files = fs.readdirSync(options.cwd); - var len = files.length, i = -1; - var res = []; - while (++i < len) { - var name = files[i]; - if (name === 'update-next') continue; - var fp = path.join(options.cwd, name); - if (isMatch(fp) || isMatch(name)) { - res.push(fp); - } + if (obj.help) { + obj._.push('help'); + delete obj.help; } - return res; + return obj; }; -/** - * Create a global path for the given value - */ - -utils.matchGlobal = function(pattern, filename) { - return utils.matchFiles(pattern, {cwd: utils.gm}); +utils.remove = function(arr, names) { + return arr.filter(function(ele) { + return names.indexOf(ele) === -1; + }); }; /** - * Resolve the correct updater module to instantiate. - * If `update` exists in `node_modules` of the cwd, - * then that will be used to create the instance, - * otherwise this module will be used. + * Return true if any of the given `tasks` are in the specified `list` */ -utils.resolveModule = function(dir) { - dir = path.join(dir, 'node_modules/', pkg.name); - if (utils.exists(dir)) { - return require(path.resolve(dir)); - } - return null; +utils.contains = function(list, tasks) { + return utils.arrayify(list).some(function(ele) { + return ~tasks.indexOf(ele); + }); }; /** - * Print a tree of "updaters" and their tasks - * - * ```js - * utils.tree(updaters); - * ``` + * Return true if the given array is empty */ -utils.tree = function(updaters) { - var res = ''; - for (var key in updaters) { - res += utils.cyan(key) + '\n'; - for (var task in updaters[key].tasks) { - res += ' - ' + task + '\n'; - } - } - return res; +utils.isEmpty = function(val) { + return utils.arrayify(val).length === 0; }; /** - * Return a list of "updaters" and their tasks - * - * ```js - * utils.list(updaters); - * ``` + * Cast `val` to an array */ -utils.list = function(updaters) { - var list = []; - for (var key in updaters) { - var updater = updaters[key]; - if (!Object.keys(updater.tasks).length) { - continue; - } - - var hasDefault = updater.tasks['default']; - var name = updater.option('name'); - var item = { - name: name + (hasDefault ? ' (default)' : ''), - value: key, - short: name + (hasDefault ? ':default' : '') - }; - list.push(item); - for (var task in updater.tasks) { - if (task === 'default') continue; - list.push({ - name: ' - ' + task, - value: key + ':' + task, - short: key + ':' + task - }); - } - } - return list; +utils.toArray = function(val) { + return typeof val === 'string' ? val.split(',') : val; }; /** - * Try to require a file + * Cast `val` to an array */ -utils.tryRequire = function(name) { - try { - return require(name); - } catch (err) { - console.log(err); - } - return null; +utils.arrayify = function(val) { + return val ? (Array.isArray(val) ? val : [val]) : []; }; /** - * Try to read a file + * Add logging methods */ -utils.tryRead = function(fp) { - try { - return fs.readFileSync(fp); - } catch (err) {} - return null; -}; +utils.logger = function(options) { + return function() { -utils.tryParse = function(str) { - try { - return JSON.parse(str); - } catch (err) {} - return {}; -}; - -utils.register = function(pattern, base, update, options) { - utils.matchFiles(pattern, options).forEach(function(fp) { - var name = utils.project(fp); - var mod = utils.resolveModule(fp) || update; - var app = mod(base.options) - .option('name', name) - .set('path', fp); - - require(utils.updatefile(fp))(app, base); - base.updater(name, app); - }); -}; - -utils.opts = function(key) { - key = key || 'opts'; - return function(app) { - var name = this.options.name || 'base'; - this.define(key, function() { - var config = this.defaults.apply(this, arguments); - return function(key, opts) { - var args = [].concat.apply([], [].slice.call(arguments)); - var prop = typeof key === 'string' ? args.shift() : null; - var val; - - if (prop && !args.length) { - val = utils.get(config, prop); - if (val) return val; - } - - var options = utils.extend.apply(utils.extend, [config].concat(args)); - return prop ? utils.get(options, prop) : options; + function logger(prop, color) { + color = color || 'dim'; + return function(msg) { + var rest = [].slice.call(arguments, 1); + return console.log + .bind(console, utils.log.timestamp + (prop ? (' ' + utils.log[prop]) : '')) + .apply(console, [utils.log[color](msg)].concat(rest)); }; - }); - }; -}; + }; -utils.defaults = function(key) { - key = key || 'defaults'; - return function(app) { - this.define(key, function() { - var args = [].concat.apply([], [].slice.call(arguments)); - args.unshift({}, this.options); - return utils.extend.apply(utils.extend, args); + Object.defineProperty(this, 'log', { + configurable: true, + get: function() { + function log() { + return console.log.apply(console, arguments); + } + log.path = function(msg) { + return logger(null, 'dim').apply(null, arguments); + }; + log.time = function(msg) { + return logger(null, 'dim').apply(null, arguments); + }; + log.warn = function(msg) { + return logger('warning', 'yellow').apply(null, arguments); + }; + log.success = function() { + return logger('success', 'green').apply(null, arguments); + }; + + log.info = function() { + return logger('info', 'cyan').apply(null, arguments); + }; + + log.error = function() { + return logger('error', 'red').apply(null, arguments); + }; + log.__proto__ = utils.log; + return log; + } }); }; }; - -/** - * Restore `require` - */ - -require = fn; - -/** - * Expose `utils` - */ - -module.exports = utils; - /** * Expose utils */ diff --git a/package.json b/package.json index d7a5814..4d142c8 100644 --- a/package.json +++ b/package.json @@ -1,156 +1,151 @@ { - "name": "update-next", - "description": "Update", - "version": "0.1.0", - "homepage": "https://github.com/jonschlinkert/update-next", + "name": "update", + "description": "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.", + "version": "0.7.4", + "homepage": "https://github.com/update/update", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", - "authors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", - "Brian Woodward (https://github.com/doowb)" - ], "contributors": [ - "Jon Schlinkert (https://github.com/jonschlinkert)", - "Brian Woodward (https://github.com/doowb)" + "Brian Woodward (https://github.com/doowb)", + "Jon Schlinkert (http://twitter.com/jonschlinkert)" ], - "repository": "jonschlinkert/update", + "repository": "update/update", "bugs": { - "url": "https://github.com/jonschlinkert/update/issues" + "url": "https://github.com/update/update/issues" }, "license": "MIT", "files": [ - "index.js", "bin", - "lib/" + "index.js", + "lib", + "LICENSE", + "README.md" ], "main": "index.js", + "preferGlobal": true, + "bin": { + "update": "bin/update.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=5.0" }, "scripts": { "test": "mocha" }, - "preferGlobal": true, - "bin": { - "update": "bin/update.js" - }, "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-gray": "^0.1.1", - "ansi-green": "^0.1.1", - "ansi-red": "^0.1.1", - "ansi-yellow": "^0.1.1", - "assemble-ask": "^0.1.4", - "assemble-core": "^0.7.0", - "assemble-loader": "^0.2.5", - "async": "^1.5.0", - "base-cli": "^0.4.0", - "base-config": "^0.3.2", - "base-methods": "^0.6.1", - "base-options": "^0.5.4", - "base-pipeline": "^0.1.4", - "base-store": "^0.3.1", - "composer-runtimes": "^0.7.0", - "cwd": "^0.9.1", - "define-property": "^0.2.5", - "delete": "^0.2.1", - "engine-base": "^0.1.2", - "expand-args": "^0.3.0", - "expand-object": "^0.4.0", - "export-files": "^2.1.0", + "arr-union": "^3.1.0", + "assemble-core": "^0.25.0", + "assemble-loader": "^0.6.1", + "base-cli-process": "^0.1.18", + "base-config-process": "^0.1.9", + "base-generators": "^0.4.5", + "base-questions": "^0.7.3", + "base-runtimes": "^0.2.0", + "base-store": "^0.4.4", + "common-config": "^0.1.0", + "data-store": "^0.16.1", + "export-files": "^2.1.1", "extend-shallow": "^2.0.1", - "for-own": "^0.1.3", - "get-value": "^2.0.2", - "global-modules": "^0.2.0", - "gulp-jshint": "^2.0.0", - "jshint-stylish": "^2.1.0", - "lazy-cache": "^1.0.2", - "load-pkg": "^3.0.1", - "look-up": "^0.8.2", - "map-config": "^0.3.0", - "matched": "^0.3.2", - "micromatch": "^2.3.7", - "minimist": "^1.2.0", - "object.omit": "^2.0.0", - "object.pick": "^1.1.1", - "parser-front-matter": "^1.3.0", - "project-name": "^0.2.3", - "question-cache": "^0.3.4", + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0", + "global-modules": "^0.2.2", + "gulp-choose-files": "^0.1.3", + "is-valid-app": "^0.2.0", + "isobject": "^2.1.0", + "lazy-cache": "^2.0.1", + "log-utils": "^0.2.1", + "parser-front-matter": "^1.4.1", "resolve-dir": "^0.1.0", - "resolve-modules": "^0.1.0", - "rimraf": "^2.4.4", - "set-value": "^0.3.2", - "sort-object-arrays": "^0.1.0", - "stream-exhaust": "^1.0.1", - "success-symbol": "^0.1.0", - "through2": "^2.0.0", - "time-stamp": "^0.1.3", - "union-value": "^0.2.1", - "update-copyright": "^0.1.0", - "use": "^1.1.2", - "write": "^0.2.1" + "resolve-file": "^0.2.0", + "set-blocking": "^2.0.0", + "strip-color": "^0.1.0", + "text-table": "^0.2.0", + "through2": "^2.0.1", + "yargs-parser": "^2.4.1" }, "devDependencies": { - "buffer-equal": "0.0.1", - "consolidate": "^0.13.1", - "coveralls": "^2.11.6", - "engine-handlebars": "^0.8.0", - "event-stream": "^3.3.2", - "graceful-fs": "^4.1.2", - "gulp": "^3.9.0", - "gulp-eslint": "^1.1.1", - "gulp-istanbul": "^0.10.3", + "base-runner": "^0.8.2", + "base-test-runner": "^0.2.0", + "base-test-suite": "^0.1.12", + "cross-spawn": "^4.0.0", + "generate-foo": "^0.1.5", + "graceful-fs": "^4.1.4", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-format-md": "^0.1.9", + "gulp-istanbul": "^1.0.0", "gulp-mocha": "^2.2.0", - "is-buffer": "^1.1.0", - "istanbul": "^0.4.1", - "kind-of": "^3.0.2", - "mocha": "^2.3.4", - "resolve-glob": "^0.1.7", - "should": "^8.0.2", - "sinon": "^1.17.2", - "swig": "^1.4.2", - "vinyl": "^1.1.0" + "gulp-unused": "^0.1.2", + "helper-changelog": "^0.3.0", + "is-absolute": "^0.2.5", + "load-pkg": "^3.0.1", + "mocha": "^2.5.3", + "npm-install-global": "^0.1.2", + "resolve": "^1.1.7", + "should": "^9.0.2", + "sinon": "^1.17.4", + "updater-example": "^0.1.2" }, "keywords": [ + "convention", + "dev", + "develop", + "fix", "lint", - "next", - "repo", + "maintain", + "manage", + "standard", + "tools", + "up-to-date", "update" ], + "update": { + "run": true, + "add": [ + "keywords" + ] + }, "verb": { + "run": true, + "toc": true, + "layout": "app", + "tasks": [ + "readme" + ], + "plugins": [ + "gulp-format-md" + ], + "helpers": [ + "helper-changelog" + ], "related": { + "description": "Update shares a common architecture and plugin ecosystem with the following libraries:", "list": [ - "composer", - "generate", - "boilerplate", - "scaffold", - "templates", - "verb", "assemble", - "update" - ], - "description": "" + "base", + "generate", + "verb" + ] }, "reflinks": [ - "scaffold", - "boilerplate", - "template", - "template", - "verb" - ] - }, - "update": { - "note": "this is just pseudo data for tests. I'll update with real stuff soon.", - "cwd": "lib/pipeline", - "helpers": { - "related": "@/helper-related" - }, - "plugins": { - "./lib/pipeline/a": {}, - "./lib/pipeline/b": {}, - "./lib/pipeline/c": {} - }, - "set": { - "aaa": "yyy" + "assemble", + "assemble-core", + "assemble-loader", + "base", + "consolidate", + "generate", + "gulp", + "handlebars", + "helper-changelog", + "lodash", + "pug", + "swig", + "templates", + "updater-example", + "verb", + "vinyl" + ], + "lint": { + "reflinks": true } } } diff --git a/support/assemblefile.js b/support/assemblefile.js new file mode 100644 index 0000000..44c2c13 --- /dev/null +++ b/support/assemblefile.js @@ -0,0 +1,31 @@ +'use strict'; + +var path = require('path'); +var generators = require('base-generators'); +var paths = require('./lib/paths'); +var lib = require('./lib'); + +module.exports = function(app) { + var dest = paths.site(); + app.use(generators()); + app.use(lib.common()); + app.register('verb', require('./verbfile')); + + app.task('verb', function(cb) { + app.generate('verb', cb); + }); + + app.task('default', ['verb'], function() { + app.layouts(paths.tmpl('layouts/*.hbs')); + app.includes(paths.tmpl('includes/*.hbs')); + app.pages(paths.docs('**/*.md')); + return app.toStream('pages') + .pipe(lib.plugins.buildPaths(dest)) + .pipe(lib.plugins.lintPaths(dest)) + .pipe(app.renderFile()) + .pipe(app.dest(function(file) { + // file.path = path.join(file.dirname, file.stem, 'index.html'); + return dest; + })); + }); +}; diff --git a/support/docs/api.plugins.md b/support/docs/api.plugins.md new file mode 100644 index 0000000..d6558c4 --- /dev/null +++ b/support/docs/api.plugins.md @@ -0,0 +1,63 @@ +--- +title: Plugins +related: + api: ['updater', 'register'] + doc: [] +--- + +A plugin is function that takes an instance of `Update` and is registered with the `.use` method. See the [base-plugins][] documentation for additional details. + +### .use + +The `.use` method is used for registering plugins that should be immediately invoked. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above +} + +app.use(plugin); +``` + +Once a plugin is invoked, it will not be called again. + +### .run + +If a plugin returns a function after it's invoked by `.use`, the function will be pushed onto an array allowing it to be called again by the `.run` method. + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +function plugin(app) { + // "app" and "this" both expose the instance of update we created above + return plugin; +} + +app.use(plugin); +``` + +We can now run all plugins that were pushed onto the `.fns` array on any arbitrary object: + +```js +var obj = {}; +app.run(obj); +``` + +Additionally: + +* If `obj` has a `.use` method, it will be used on each plugin (e.g. `obj.use(fn)`). Otherwise `fn(obj)`. +* If the plugin returns a function again and `obj` has a `.run` method, the plugin will be pushed onto the `obj.fns` array. + +This can continue indefinitely as long as the plugin returns a function and the receiving object has `.use`/`.run` functions. + +## Updaters + +When plugins are [registered by name](docs/updaters.md), they are referred to as "updaters". See the [updater documentation](docs/updaters.md) for more details. diff --git a/support/docs/api.register.md b/support/docs/api.register.md new file mode 100644 index 0000000..d704e12 --- /dev/null +++ b/support/docs/api.register.md @@ -0,0 +1,32 @@ +--- +title: Register +related: + api: ['updater', 'plugins'] +--- + +Register an updater function by name. Similar to [.updater](updater.md) but does not invoke the updater function. + +```js +app.register(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `fn` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// not invoked until called by `.update` +app.register('foo', function(app) { + // do updater stuff +}); + +app.update('foo', function(err) { + if (err) return console.log(err); +}); +``` diff --git a/support/docs/api.updater.md b/support/docs/api.updater.md new file mode 100644 index 0000000..287e16d --- /dev/null +++ b/support/docs/api.updater.md @@ -0,0 +1,34 @@ +--- +title: Updater +related: + cli: ['commands'] + api: ['register', 'plugins'] + doc: ['faq'] +--- + +Register an updater function by name. Similar to [.register](register.md) but immediately invokes the updater function upon registering it. + +```js +app.updater(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the updater to register +* `updater` **{Function}**: updater function + +**Example** + +```js +var Update = require('update'); +var app = new Update(); + +// immediately invoked +app.updater('bar', function(app) { + // do updater stuff +}); + +app.update('bar', function(err) { + if (err) return console.log(err); +}); +``` diff --git a/support/docs/cli.built-in-tasks.md b/support/docs/cli.built-in-tasks.md new file mode 100644 index 0000000..56d5858 --- /dev/null +++ b/support/docs/cli.built-in-tasks.md @@ -0,0 +1,55 @@ +--- +title: Built in tasks +related: + doc: [] +--- + +Update only has a few built-in [tasks](docs/tasks.md) (these might be externalized at some point): + +* [init](#init): Choose the updaters to run by default each time `update` is run from the command line +* [list](#list): List all globally and locally installed updaters +* [show](#show): show the list of updaters that will run on the current project when the `update` command is given +* [new](#new): create a new `updatefile.js` in the current working directory +* [help](#help): show a help menu with all available commands + +## Usage + +### init + +Prompts you to choose one or more "default updaters" to run automatically each time to `update` command is given: + +```sh +$ update init +``` + +### list + +List all globally and locally installed updaters: + +```sh +$ update list +``` + +### show + +Show the list of updaters that will run on the current project when the `update` command is given: + +```sh +$ update show +``` + +### new + +Create a new `updatefile.js` in the current working directory: + +```sh +$ update new +``` + +### help + +Display a help menu with all available commands: + +```sh +$ update help +``` diff --git a/support/docs/cli.commands.md b/support/docs/cli.commands.md new file mode 100644 index 0000000..8dc6d92 --- /dev/null +++ b/support/docs/cli.commands.md @@ -0,0 +1,51 @@ +--- +title: Command line flags +related: + docs: [''] +--- + +Supported command line flags. + +## --run + +By default, when an `updatefile.js` exists in the current working directory, the `update` command will only run explicitly specified tasks or, if no tasks are explicitly defined, the `default` task in `updatefile.js`. + +The `--run` flag forces `update` to run stored tasks and the `default` task or explicitly specified tasks in `updatefile.js`. Stored tasks are executed first, in the order defined, then the `default` task or explicitly defined tasks. + +**Example** + +```sh +$ update --run +``` + +## --help + +See a help menu in the terminal: + +```sh +Usage: update [options] + +Command: Updater or tasks to run + +Examples: + + # run the "foo" updater + $ update foo + + # run the "bar" task on updater "foo" + $ update foo:bar + + # run multiple tasks on updater "foo" + $ update foo:bar,baz,qux + + # run a sub-updater on updater "foo" + $ update foo.abc + + # run task "xyz" on sub-updater "foo.abc" + $ update foo.abc:xyz + + Update attempts to automatically determine if "foo" is a task or updater. + If there is a conflict, you can force update to run updater "foo" + by specifying a task on the updater. Example: `update foo:default` +``` + diff --git a/support/docs/faq.md b/support/docs/faq.md new file mode 100644 index 0000000..8eab822 --- /dev/null +++ b/support/docs/faq.md @@ -0,0 +1,21 @@ +--- +title: Faq +related: + doc: ['features'] +--- + +## How is the name written? + +Update, with a capital "U", the rest lowercase. `updatefile.js` is all lowercase, no capital letters. + + + +## Aliases + +**What's an updater's alias, and what do they do?** + +Update tries to find globally installed updaters using an "alias" first, falling back on the updater's full name if not found by its alias. + +An updater's alias is created by stripping the substring `updater-` from the _full name_ of updater. Thus, when publishing an updater the naming convention `updater-foo` should be used (where `foo` is the alias, and `updater-foo` is the full name). + +Note that **no dots may be used in published updater names**. Aside from that, any characters considered valid by npm are fine. diff --git a/support/docs/features.md b/support/docs/features.md new file mode 100644 index 0000000..86780be --- /dev/null +++ b/support/docs/features.md @@ -0,0 +1,22 @@ +--- +title: Features +related: + doc: ['faq'] +--- + +Update offers an elegant and robust suite of methods, carefully organized to help you accomplish common activities in less time, including: + +* **unparalleled flow control**: through the use of [updaters][getting-started], [sub-updaters][getting-started] and [tasks][getting-started] +* **templates, scaffolds and boilerplates**: update a single file, initialize an entire project, or provide ad-hoc "components" throughout the duration of a project using any combination of [templates, scaffolds and boilerplates](#templates-scaffolds-and-boilerplates) +* **any engine**: use any template engine to render templates, including [handlebars][], [lodash][], [swig][] and [pug][] +* **prompts**: asks you for data when it can't find what it needs, and it's easy to customize prompts for any data you want +* **data**: gathers data from the user's environment to populate "hints" in user prompts and to use when rendering templates +* **streams**: interact with the file system, with full support for [gulp][] and [assemble][] plugins +* **smart plugins**: Update is built on [base][], so any "smart" plugin can be used +* **stores**: persist configuration settings, global defaults, project-specific defaults, answers to prompts, and so on + +Visit the [getting started guide][getting-started] to learn more. + + + +[getting-started]: https://github.com/update/getting-started diff --git a/support/docs/installing-the-cli.md b/support/docs/installing-the-cli.md new file mode 100644 index 0000000..b65bad9 --- /dev/null +++ b/support/docs/installing-the-cli.md @@ -0,0 +1,15 @@ +--- +title: Installing the cli +related: + doc: ['installing-updaters'] +--- + +To run update from the command line, you'll need to install Update's CLI globally first. You can do that now with the following command: + +```sh +$ npm install --global update +``` + +This adds the `update` command to your system path, allowing it to be run from any directory. + +You should now be able to use the `update` command to execute code in a local `updatefile.js` file, or to run any locally or globally installed updaters by their [aliases](tasks.md#alias-tasks) or full names. diff --git a/support/docs/installing-updaters.md b/support/docs/installing-updaters.md new file mode 100644 index 0000000..cd053ba --- /dev/null +++ b/support/docs/installing-updaters.md @@ -0,0 +1,9 @@ +--- +title: Installing updaters +related: + doc: ['installing-the-cli'] +--- + +Updaters are responsible for all of the "updating" that happens in update. You can find updaters to install by [searching npm](https://www.npmjs.com/browse/keyword/update-updater) for packages that have the keyword `update-updater`. + +TODO diff --git a/support/docs/introduction.md b/support/docs/introduction.md new file mode 100644 index 0000000..0de03da --- /dev/null +++ b/support/docs/introduction.md @@ -0,0 +1,89 @@ +--- +title: Introduction +draft: true +related: + doc: ['updaters', 'updatefile', 'tasks', 'features', 'faq'] +--- + + + +## What is update? + +Update is a new, open-source developer framework for automating updates of any kind in code projects. + +* normalize configuration settings, verbiage, or preferences across all of your projects +* update files that are typically excluded from the automated parts of the software lifecycle, and are often forgotten about after they're created. +* fix dates in copyrights, licenses and banners +* removing deprecated fields from project manifests +* updating settings in runtime config files, preferences in dotfiles, and so on. + +## How does it work? + +Update's API has methods for [creating](#creating-updaters), [registering](#registering-updaters), [resolving](#resolving-updaters) and [running](#running-updaters) updaters. + +### Who should use Update? + +* developers or organizations with many projects under their stewardship +* agencies or consultants who maintain and/or create client projects and would like to reduce time spent on maintainance +* anyone who cares about having consistency across all of their projects + +## Updaters + +All "updates" are accomplished using plugins called [updaters](#updaters). + +**What are updaters?** + +- Updaters are functions that are registered by name, and can be run by [command line](#command-line) or [API](#api). +- Updaters may be published to [npm](https://www.npmjs.com) using the `updater-foo` naming convention, where `foo` is the [alias](#aliases) of your updater. +- Published updaters can be installed locally or globally. + +## Command line + +### Running updaters + +To run updaters by command line, pass the [aliases](#aliases) or full [npm](https://www.npmjs.com) package names (if published) of the updaters to run after the `update` command. + +**Example** + +Run updaters `foo`, `bar` and `baz` in series: + +```sh +$ update foo bar baz +# or +$ update updater-foo updater-bar updater-baz +``` + +Note that _updaters are run in series_, so given the previous example, updater `bar` will not run until updater `foo` is completely finished executing. + +### Aliases + +Get the alias of an updater by removing the `updater-` substring from the begining of the full name. + +## Resolving updaters + +When run by command line, Update's CLI will attempt to find and run updaters matching the names you've given, by first searching in the local `updatefile.js`, then using node's `require()` system to find locally installed updaters, and last by searching for globally installed updaters. + +If any of the updaters specified is not found, _an error is thrown and the process will exit_. + +## API + +by passing the names of updaters to run to the `.update` + +## Update + +Updaters via CLI or API. (tasks are powered by [bach][], the same library used in [gulp][] v4.0). + +The main export of the library is the `Update` constructor function. + +Updaters themselves are just functions that take an instance of `Update`. + +Update gives you a way to automate the maintenance of files that are typically excluded from the automated parts of the software lifecycle, and thus are mostly forgotten about after they're created. + +For example, if we were to sift the files in the average code project into major generic buckets we would end up with something like this: + +* **code**: the actual source code of the project (compiled, lib, src, and so on) +* **dist**: the "deliverable" of the project (this could be HTML, CSS, minified JavaScript, or something similar for non-web projects) +* **docs**: documentation for the project +* **everything else**: LICENSE and copyright files, dotfiles, manifests, config files, and so on. + +Update maintains **everything else**. diff --git a/support/docs/layouts/default.md b/support/docs/layouts/default.md new file mode 100644 index 0000000..a06e910 --- /dev/null +++ b/support/docs/layouts/default.md @@ -0,0 +1,5 @@ +# <%= title %> + +{% body %} + +<%= relatedLinks(related) %> diff --git a/support/docs/nested-updaters.md b/support/docs/nested-updaters.md new file mode 100644 index 0000000..1006132 --- /dev/null +++ b/support/docs/nested-updaters.md @@ -0,0 +1,53 @@ +--- +title: Nested updaters +--- + +Updaters provide a convenient way of wrapping code that should be executed on-demand, whilst also "namespacing" the code being wrapped, and making it available to be executed using a consistent and intuitive syntax by either CLI or API. + + +TBC... + + +## TODO + +- [ ] explain how nested updaters work +- [ ] command line syntax +- [ ] API syntax + +## Pre-requisites + +- [plugins](api/plugins.md) +- [updaters](updaters.md) + +## Sub-updaters + +As with [plugins](api/plugins.md), updaters may be nested: _any updater can register other updaters, and any updater can be registered by other updaters._ We refer to nested updaters as **sub-updaters**. + +**Example** + +```js +app.register('foo', function(foo) { + // do updater stuff + this.register('bar', function(bar) { + // do updater stuff + this.register('baz', function(baz) { + // do updater stuff + this.task('default', function(cb) { + console.log(baz.namespace); + cb(); + }); + }); + }); +}); +``` + +## Run nested updaters + +Use dot-notation to get the updater you wish to run: + +```js +app.update('foo.bar.baz', function(err) { + if (err) return console.log(err); + +}); +``` diff --git a/support/docs/redirects.json b/support/docs/redirects.json new file mode 100644 index 0000000..63b9ef9 --- /dev/null +++ b/support/docs/redirects.json @@ -0,0 +1,3 @@ +{ + "old": "new" +} diff --git a/support/docs/symlinking-updaters.md b/support/docs/symlinking-updaters.md new file mode 100644 index 0000000..aca2717 --- /dev/null +++ b/support/docs/symlinking-updaters.md @@ -0,0 +1,71 @@ +--- +title: Symlinking updaters +related: + doc: ['tasks', 'updatefile', 'installing-updaters', 'updaters'] +--- + +While developing [updaters](updaters.md), you might find it useful to symlink them to global `node_modules` so that Update's CLI will find them and run them, as if they had been installed from npm using `npm install --global`. + +The following example shows you to do this. + +## Example + +**1. Create an updater project** + +Create a new project named `updater-aaa`. You can expedite this using [generate][] or Google's Yeoman or however you prefer. + +**2. Add `index.js`** + +In `index.js`, add the following code: + +```js +// -- index.js -- +module.exports = function(app) { + app.task('default', function(cb) { + console.log('updater', app.name, '> task', this.name); + cb(); + }); +}; +``` + +- `app.name` will display the name of the updater being run +- `this.name` will display the name of the task being run + +_(Also make sure the `index.js` is listed in the `main` property in package.json, so that node's `require()` system finds the file)_ + +**3. Symlink** + +Next, we need to symlink the module to global `node_modules`, so that `updater-aaa` is discoverable by Update's CLI. + +From the root of the `updater-aaa` project, run the following command: + +```sh +$ npm link +``` + +**4. Run** + +To test that `updater-aaa` was symlinked properly, run the following command: + +```sh +$ update aaa +``` + +You should see something like the following in the terminal + +```sh +updater updater-aaa > task default +``` + +If not, review the steps and make sure you did everything described. If you still can't get it working please [create an issue](../../../issues) so we can look into it. + +**Next steps** + +If you'd like to see how multiple updaters can work together, repeat the same steps described above to create and symlink `updater-bbb` and `updater-ccc`. + +Then run: + +```sh +update aaa bbb ccc +``` + diff --git a/support/docs/tasks.md b/support/docs/tasks.md new file mode 100644 index 0000000..1392a01 --- /dev/null +++ b/support/docs/tasks.md @@ -0,0 +1,171 @@ +--- +title: Tasks +related: + doc: + - link: updaters + title: Running updaters + anchor: '#running-updaters' + - updaters + - updatefile +--- + +Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. + + + +## Creating tasks + +Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. + +```js +app.task('foo', function(cb) { + // since tasks are asynchronous, you must call the callback when the task is complete + cb(); +}); +``` + +## Running tasks + +Tasks can be run by command line or API. + +### Command line + +Pass the names of the tasks to run after the `update` command. + +**Examples** + +Run task `foo`: + +```sh +update foo +``` + +Run tasks `foo`, `bar` and `baz`: + +```sh +update foo bar baz +``` + +**Conflict resolution** + +You might notice that [updaters](updaters.md) can also be run from the command line using the same syntax. Update can usually determine whether you meant to call tasks or updaters. Visit the [running updaters](updaters.md#running-updaters) documentation for more information. + +### Task API + +#### .task + +Create a task: + +```js +app.task(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the task to register +* `fn` **{Function}**: asynchronous callback function, or es6 generator function + +**Example** + +```js +app.task('default', function(cb) { + // do task stuff (be sure to call the callback) + cb(); +}); +``` + +**Stream or callback** + +When using update's file system API (`.src`/`.dest` etc), you can optionally return a stream instead of calling a callback. Either a callback must be called, or a stream must be returned, otherwise update has no way of knowing when a task is complete. + +#### .build + +Run one or more tasks. + +**Params** + +* `names` **{String|Array|Function}**: names of one or more tasks to run, or callback function if you only want to run the [default task](#default-task) +* `callback`: callback function, invoked after all tasks have finished executing. The callback function exposes `err` as the only argument, with any errors that occurred during the execution of any tasks. + +**Example** + +```js +app.task('foo', function(cb) { + // do task stuff + cb(); +}); + +app.task('bar', function(cb) { + // do task stuff + cb(); +}); + +app.build(['foo', 'bar'], function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +#### .update + +The `.update` method may also be used to run tasks. However, `.update` can be used to run _tasks and updaters_, thus it will also look for updaters to run when a task is not found. + +_To ensure that only tasks are run, use the `.build` method._ + +See the [updaters documentation](updaters.md) for more details. + +### Task composition + +#### Task dependencies + +When a task has "dependencies", this means that one or more other tasks need to finish before the task is executed. + +Dependencies can be passed as the second argument to the `.task` method. + +**Example** + +In the following example, task `foo` has dependencies `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz'], function(cb) { + // do task stuff + cb(); +}); +``` + +Task `foo` will not execute until tasks `bar` and `baz` have completed. + +#### Alias tasks + +An "alias" task is a task with one or more dependencies and _no callback_. + +**Example** + +In this example, task `foo` is an alias for tasks `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz']); +``` + +In this example, task `foo` is an alias for task `baz` + +```js +app.task('foo', ['baz']); +``` + +### default task + +The `default` task is run automatically when a callback is passed as the only argument: + +```js +app.task('default', function(cb) { + // do task stuff + cb(); +}); + +// no need to specify "default", but you can if you want +app.build(function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` diff --git a/support/docs/tutorial.md b/support/docs/tutorial.md new file mode 100644 index 0000000..e3cdcd9 --- /dev/null +++ b/support/docs/tutorial.md @@ -0,0 +1,116 @@ +--- +title: Tutorial +draft: true +related: + doc: [] +--- + +The following intro only skims the surface of what update has to offer. For a more in-depth introduction, we highly recommend visiting the [getting started guide][getting-started]. + +**Create an updater** + +Add a `updatefile.js` to the current working directory with the following code: + +```js +module.exports = function(app) { + console.log('success!'); +}; +``` + +**Run an updater** + +Enter the following command: + +```sh +update +``` + +If successful, you should see `success!` in the terminal. + +**Create a task** + +Now, add a task to your updater. + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update default +``` + +When a local `updatefile.js` exists, the `update` command is aliased to automatically run the `default` task if one exists. But you can also run the task with `update default`. + +**Run a task** + +Let's try adding more tasks to your updater: + +```js +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default > success!'); + cb(); + }); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +Now, in the command line, run: + +```sh +$ update +# then try +$ update foo +# then try +$ update foo bar +``` + +**Run task dependencies** + +Now update your code to the following: + +```js +module.exports = function(app) { + app.task('default', ['foo', 'bar']); + + app.task('foo', function(cb) { + console.log('foo > success!'); + cb(); + }); + + app.task('bar', function(cb) { + console.log('bar > success!'); + cb(); + }); +}; +``` + +And run: + +```sh +$ update +``` + +You're now a master at running tasks with update! You can do anything with update tasks that you can do with [gulp][] tasks (we use and support gulp libraries after all!). + +**Next steps** + +Update does much more than this. For a more in-depth introduction, we highly recommend visiting the [getting started guide](https://github.com/update/getting-started). \ No newline at end of file diff --git a/support/docs/updatefile.md b/support/docs/updatefile.md new file mode 100644 index 0000000..e4ef3b0 --- /dev/null +++ b/support/docs/updatefile.md @@ -0,0 +1,81 @@ +--- +title: Updatefile +related: + doc: ['installing-updaters', 'updaters', 'tasks'] +--- + +If you're authoring (and maybe even publishing) an updater, you can use the same conventions you would with any other node.js library. If node.js/npm can find your module, then so can Update. You can write your code in `index.js`, or `lib/foo.js` or wherever you want. + +However, Update's CLI shows a little unfair favoratism for files with the name `updatefile.js`. + +When the current working directory has an `updatefile.js`, Update's CLI will try to run any tasks or code in that file _instead of running your [default updaters](cli/built-in-tasks.md#init)_. + +**Force default updaters to run** + +You can force Update's CLI to run all of your default updaters by passing the `--run` flag on the command line, or to _always run default updaters in a project that has an `updatefile.js`, you can add the following in package.json: + +```json +{ + "update": { + "run": true + } +} +``` + +Note that that this will run _both_ the default updaters, and the `updatefile.js`, in that order. + +## How the CLI uses updatefile.js + +Each time `update` is run, Update's CLI looks for an `updatefile.js` in the current working directory. + +**If `updatefile.js` exists** + +Update's CLI attempts to: + +* Load a local installation of the Update library using node's `require()` system, falling back to global installation if not found. +* Load the configuration from `updatefile.js` using node.js `require()` system +* Register it as the ["default" updater](updaters.md#default-updater) +* Execute any tasks or updaters you've specified for it to run. +* If multiple task or updater names are specified on the command line, Update's CLI will attempt to run all of the specified tasks and updaters. + +**If `updatefile.js` does not exist** + +Update's CLI attempts to: + +* Find any updaters you've specified for it to run by using node's `require()` system to search for locally and globally installed modules with the name `updater-*`. + + +## Creating an updatefile.js + +An `updatefile.js` may contain any custom JavaScript code, but must export a function that takes an instance of Update (`app`): + +**Example** + +```js +// -- updatefile.js -- +module.exports = function(app) { + // custom code here +}; +``` + +Inside this function, you can define [tasks](tasks.md), additional [updaters](updaters.md), or any other custom JavaScript code necessary for your updater: + +```js +module.exports = function(app) { + // register a task + app.task('default', function(cb) { + // do task stuff + cb(); + }); + + // register an updater + app.register('foo', function() { + + }); + + // register another updater + app.register('bar', function() { + + }); +}; +``` diff --git a/support/docs/updaters.md b/support/docs/updaters.md new file mode 100644 index 0000000..f2a8a80 --- /dev/null +++ b/support/docs/updaters.md @@ -0,0 +1,232 @@ +--- +title: Updaters +related: + doc: ['tasks', 'updatefile', 'installing-updaters', 'symlinking-updaters'] +--- + +This document describes how to create, register and run updaters. + + + +## TODO + +- [ ] document updater args +- [ ] explain how the `base` instance works +- [ ] document `env` + + +## What is an updater? + +Updaters are [plugins](api/plugins.md) that are registered by name. If you're not familiar with plugins yet, it might be a good idea to review the [plugins docs](api/plugins.md) first. + +The primary difference between "updaters" and "plugins" is how they're registered, but there are a few other minor differences: + +| | **Plugin** | **Updater** | +| --- | --- | --- | +| Registered with | [.use](api/plugins.md#use) method | [.register](#register) method or [.updater](#updater) method | +| Instance | Loaded onto "current" `Update` instance | A `new Update()` instance is created for every updater registered | +| Invoked | Immediately | `.register` deferred (lazy), `.updater` immediately | +| Run using | [.run](api/plugins.md#run): all plugins are run at once | `.update`: only specified plugin(s) are run | + +## Creating updaters + +An updater function takes an instance of `Update` as the first argument. + +**Example** + +```js +function updater(app) { + // do updater stuff +} +``` + + + +## Registering updaters + +Updaters may be registered using either of the following methods: + +* `.register`: if the plugin should not be invoked until it's called by `.update` (stays lazy while it's cached, this is preferred) +* `.updater`: if the plugin needs to be invoked immediately when registered + +### .register + +Register an updater function with the given `name` using the `.register` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is run with the `.update` method. + console.log('foo is being run'); +} + +// register as an updater with the `.register` method +app.register('foo', updater); + +// run the `foo` updater with the `.update` method +app.update('foo', function(err) { + if (err) return console.log(err); +}); +//=> "foo is being run" +``` + +### .updater + +Register an updater function with the given `name` using the `.updater` method. + +**Example** + +```js +var update = require('update'); +var app = update(); + +function updater(app) { + // do updater stuff when the updater is registered + console.log('foo is being registered'); +} + +// register as an updater using `.updater` +app.updater('foo', updater); +//=> "foo is being registered" +``` + +**Should I use `.updater` or `.register`?** + +In general, it's recommended that you use the `.register` method. In most cases update is smart enough to figure out when to invoke updater functions. + +However, there are always exceptions. If you create custom code and notice that update can't find the information it needs. Try using the `.updater` method to invoke the function when the updater is registered. + +## Running updaters + +Updaters and their tasks can be run by command line or API. + +**Command line** + +To run globally or locally installed `updater-foo`, or an updater named `foo` in `updatefile.js`, run: + +```sh +$ update foo +``` + +**API** + +```js +var update = require('update'); +var app = update(); + +function fn() { + // do updater stuff +} + +// the `.register` method does not invoke the updater +app.register('foo', fn); + +// the `.updater` method invokes the updater immediately +app.updater('bar', fn); + +// run updaters foo and bar in series (both updaters will be invoked) +app.update(['foo', 'bar'], function(err) { + if (err) return console.log(err); +}); +``` + +## Resolving updaters + +Updaters can be published to npm and installed globally or locally. But there is no requirement that updaters must be published. You can also create custom updaters and register using the [.register](#register) or [.updater](#updater) methods. + +This provides a great deal of flexibility, but it also means that we need a strategy for _finding updaters_ when `update` is run from the command line. + +### Tasks and updaters + +1. When both a task and an updater have the same name _on the same instance_, Update will always try to run the task first (this is unlikely to happen unless you intend for it to - there are [reasons to do this](#naming-tips)) + +### Naming tips + +Since the [.build](tasks.md#build) method only runs tasks, you can use this to your advantage by aliasing sub-generators with tasks. + +**Don't do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.build` doesn't run updaters + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Do this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + // `.update` will run updater `foo` + app.update('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +**Or this** + +```js +module.exports = function(app) { + app.register('foo', function(foo) { + foo.task('default', function(cb) { + // do task stuff + cb(); + }); + }); + + app.task('foo', function(cb) { + app.update('foo', cb); + }); + + // `.build` will run task `foo`, which runs updater `foo` + app.build('foo', function(err) { + if (err) return console.log(err); + }); +}; +``` + +### Order of precendence + +When the command line is used, Update's CLI resolves updaters in the following order: + +1. [default updater](#default-updater): attempts to match given names to updaters and tasks registered on the `default` updater +2. built-in updaters: attempts to match given names to Update's [built-in updaters](cli/built-in-updaters.md) +3. locally installed updaters +4. globally installed updaters + +## Discovering updaters + +todo + +## Default updater + +If an updater is registered with the name `default` it will receive special treatment from Update and Update's CLI. More specifically, when Update's CLI looks for updaters or tasks to run, it will search for them on the `default` updater first. + +There is a catch... + +**Registering the "default" updater** + +_The only way to register a `default` updater is by creating an [updatefile.js](updatefile.md) in the current working directory._ + +When used by command line, Update's CLI will then use node's `require()` system to get the function exported by `updatefile.js` and use it as the `default` updater. diff --git a/support/lib/_drafts/matter.js b/support/lib/_drafts/matter.js new file mode 100644 index 0000000..9597eaa --- /dev/null +++ b/support/lib/_drafts/matter.js @@ -0,0 +1,29 @@ + + // app.onLoad(/\.md$/, function(view, next) { + // if (/^#/.test(view.content)) { + // view.content = view.content.replace(/^#[^\n]+/, ''); + // } + // var data = '---\ntitle: '; + // var title = view.stem; + // var segs = title.split('.'); + // if (segs.length > 1) { + // title = segs[1]; + // } + // title = title.split('-').join(' '); + // title = title.charAt(0).toUpperCase() + title.slice(1); + // data += title; + // data += '\n'; + // data += 'layout: default\n'; + // data += 'related:\n'; + // data += ' doc: []\n'; + // data += '---\n\n'; + // view.content = data + view.content; + // next(); + // }); + + // app.task('default', function() { + // app.pages('docs/**/*.md'); + // return app.toStream('pages') + // // .pipe(app.renderFile()) + // .pipe(app.dest('docs')); + // }); diff --git a/support/lib/common.js b/support/lib/common.js new file mode 100644 index 0000000..b43179d --- /dev/null +++ b/support/lib/common.js @@ -0,0 +1,22 @@ +'use strict'; + +var path = require('path'); +var helpers = require('./helpers'); +var isValid = require('is-valid-app'); +var pkg = require('base-pkg'); + +module.exports = function(options) { + return function(app) { + if (!isValid(app, 'update-support-common')) return; + app.use(require('generate-collections')); + app.use(require('generate-defaults')); + app.use(require('verb-toc')); + app.use(helpers()); + app.use(pkg()); + + if (!app.docs) app.create('docs'); + app.option('renameKey', function(key, file) { + return file ? file.basename : path.basename(key); + }); + }; +}; diff --git a/support/lib/helpers.js b/support/lib/helpers.js new file mode 100644 index 0000000..9fbea96 --- /dev/null +++ b/support/lib/helpers.js @@ -0,0 +1,146 @@ +'use strict'; + +var path = require('path'); +var utils = require('./utils'); + +module.exports = function(options) { + return function(app) { + if (!utils.isValid(app, 'update-support-helpers')) return; + app.helpers(utils.helpers()); + app.helper('raw', function(str) { + console.log(str); + return str; + }); + + app.helper('hasValue', function(val, str) { + return utils.hasValue(val) ? str : ''; + }); + + app.helper('hasAny', function(arr, str) { + arr = utils.arrayify(arr); + var len = arr.length; + var idx = -1; + while (++idx < len) { + var ele = arr[idx] || []; + if (ele.length) { + return str; + } + } + return ''; + }); + + app.helper('relatedLinks', function(related) { + if (!related || typeof related !== 'object') { + return ''; + } + var links = app.getHelper('links').bind(this); + return relatedLinks(related, links); + }); + + app.helper('links', function(related, prop) { + var arr = related[prop] || (related[prop] = []); + arr = utils.arrayify(arr); + if (arr.length === 0) { + return ''; + } + + var fp = this.view.stem; + var dir = 'docs'; + var segs = fp.split('.'); + if (segs.length > 1) { + dir = segs[0]; + } + var links = arr.map(function(link) { + return createLink(dir, prop, link); + }); + return links.join('\n'); + }); + }; +}; + +function relatedLinks(related, links) { + var keys = Object.keys(related); + if (keys.length === 0) { + return ''; + } + + var hasLinks = false; + function reduce(acc, key) { + if (related[key].length === 0) { + return acc; + } + + hasLinks = true; + acc += `**${heading(key)}**\n${links(related, key)}\n`; + return acc; + } + + var list = `${keys.reduce(reduce, '')}`; + + if (!hasLinks) { + return ''; + } + + return `## Related\n${list}`; +} + +function createLink(dir, prop, link) { + var key = (prop === 'doc') ? 'docs' : prop; + link = normalizeLink(link); + + if (dir !== key) { + if (key === 'docs') { + key = ''; + } + link.filename = path.join('..', key, link.filename); + } else if (key !== 'docs' && dir !== key) { + link.filename = path.join(key, link.filename); + } + return `- [${link.title}](${link.filename}${link.anchor})`; +} + +function heading(title) { + switch (title.toLowerCase()) { + case 'doc': + title = 'docs'; + break; + case 'url': + title = 'links'; + break; + default: + if (/(i|s)$/.test(title.toLowerCase()) === false) { + title += 's'; + } + break; + } + + if (/s$/.test(title)) { + return utils.pascalcase(title); + } + return title.toUpperCase(); +} + +function normalizeLink(obj) { + if (typeof obj === 'string') { + obj = {link: obj}; + } + var link = obj.link; + var filename = link; + var name = link; + var anchor = ''; + var segs = link.split('#'); + if (segs.length === 2) { + name = segs[segs.length - 1]; + anchor = '#' + name; + filename = segs[0]; + } + + if (!/\.md$/.test(filename) && !/#/.test(filename)) { + filename += '.md'; + } + + obj.title = obj.title || name; + obj.filename = obj.filename || filename; + obj.anchor = obj.anchor || anchor; + return obj; +} diff --git a/lib/tasks/index.js b/support/lib/index.js similarity index 63% rename from lib/tasks/index.js rename to support/lib/index.js index 23b2930..3559238 100644 --- a/lib/tasks/index.js +++ b/support/lib/index.js @@ -1 +1,2 @@ +require('set-blocking')(true); module.exports = require('export-files')(__dirname); diff --git a/support/lib/middleware.js b/support/lib/middleware.js new file mode 100644 index 0000000..5dac7d2 --- /dev/null +++ b/support/lib/middleware.js @@ -0,0 +1,55 @@ +'use strict'; + +var path = require('path'); +var isValid = require('is-valid-app'); +var utils = require('./utils'); + +module.exports = function(options) { + return function(app) { + if (!isValid(app, 'update-support-middleware')) return; + app.cache.views = {docs: []}; + + app.onLoad(/\.md$/, function(view, next) { + var related = view.data.related || (view.data.related = {}); + related.doc = utils.arrayify(related.doc); + related.api = utils.arrayify(related.api); + related.cli = utils.arrayify(related.cli); + related.url = utils.arrayify(related.url); + + if (view.content.indexOf('') !== -1) { + view.data.toc = true; + } + + view.data.layout = 'default'; + if (view.data.toc === true) { + view.data.toc = {render: true}; + } + if (view.stem.indexOf('docs.') === 0) { + app.cache.views.docs.push(view); + } + next(); + }); + + app.preRender(/docs[\\\/][^\\\/]+\.md$/, function(view, next) { + if (typeof view.data.title === 'undefined') { + next(new Error('`title` is missing in ' + view.path)); + return; + } + next(); + }); + + app.preWrite(/\.md$/, function(file, next) { + var segs = file.stem.split('.').filter(Boolean); + if (segs[0] === 'docs') { + segs.shift(); + file.stem = segs[0]; + } + if (segs.length > 1) { + file.path = path.resolve(file.base, segs.join('/') + file.extname); + } + file.content = file.content.trim(); + file.content += '\n'; + next(); + }); + }; +}; diff --git a/support/lib/paths.js b/support/lib/paths.js new file mode 100644 index 0000000..11b64a6 --- /dev/null +++ b/support/lib/paths.js @@ -0,0 +1,21 @@ +'use strict'; + +var path = require('path'); +var base = path.resolve(__dirname, '..'); +exports.cwd = require('memoize-path')(base); +exports.memo = require('memoize-path')(base); + +exports.docs = function(fp) { + var res = exports.memo('../docs')(fp); + return fp ? res() : res; +}; + +exports.site = function(fp) { + var res = exports.memo('../_gh_pages')(fp); + return fp ? res() : res; +}; + +exports.tmpl = function(fp) { + var res = exports.memo('templates')(fp); + return fp ? res() : res; +}; diff --git a/support/lib/plugins.js b/support/lib/plugins.js new file mode 100644 index 0000000..c2e515d --- /dev/null +++ b/support/lib/plugins.js @@ -0,0 +1,80 @@ +'use strict'; + +var path = require('path'); +var utils = require('./utils'); + +exports.buildPaths = function(dest) { + var paths = {api: [], cli: [], docs: [], url: [], path: [], dest: [], anchors: {}}; + var files = []; + + return utils.through.obj(function(file, enc, next) { + setDir(file, paths); + getAnchors(file, paths); + createDest(file, paths, dest); + paths.path.push(file.path); + files.push(file); + next(); + }, function(cb) { + var len = files.length; + var idx = -1; + while (++idx < len) { + var file = files[idx]; + getLinks(file, paths); + file.paths = paths; + this.push(file); + } + cb(); + }); +}; + +exports.lintPaths = function(dest) { + return utils.through.obj(function(file, enc, next) { + // console.log(file.paths); + next(null, file); + }); +}; + +function setDir(file, paths) { + file.name = file.stem; + var segs = file.relative.split('/'); + var dir = 'docs'; + var rest = file.relative; + if (segs.length > 1) { + dir = segs.shift(); + rest = segs.join('/'); + } + paths[dir] = paths[dir] || []; + paths[dir].push(rest); + file.rest = rest; + file.dir = dir; +} + +function createDest(view, paths, dest) { + var file = view.clone(); + file.dirname = path.join(file.dirname, file.stem); + file.basename = 'index.html'; + paths.dest.push(path.resolve(dest, file.relative)); +} + +function getAnchors(file, paths) { + var str = file.contents.toString(); + var matches = str.match(/^#+\s+([^\n]+)/gm); + if (matches) { + paths.anchors[file.rest] = paths.anchors[file.rest] || []; + matches.reduce(function(acc, str) { + var anchor = utils.slugify(str.replace(/^#+\s+/, '')); + if (acc.indexOf(anchor) === -1) { + acc.push(anchor); + } + return acc.sort(); + }, paths.anchors[file.rest]); + } +} + +function getLinks(file, paths) { + var md = new utils.Remarkable({paths: paths, file: file}); + md.use(utils.prettify); + md.use(utils.lintLinks()); + md.render(file.content); +} + diff --git a/support/lib/utils.js b/support/lib/utils.js new file mode 100644 index 0000000..5f3c791 --- /dev/null +++ b/support/lib/utils.js @@ -0,0 +1,120 @@ +'use strict'; + +var utils = module.exports = require('lazy-cache')(require); +var rules = require('pretty-remarkable/lib/rules'); + +var fn = require; +require = utils; + +require('is-valid-app', 'isValid'); +require('has-value'); +require('pretty-remarkable', 'prettify'); +require('remarkable', 'Remarkable'); +require('strip-color'); +require('pascalcase'); +require('template-helpers', 'helpers'); +require('through2', 'through'); + +utils.arrayify = function(val) { + return val ? (Array.isArray(val) ? val : [val]) : []; +}; + +/** + * Slugify the url part of a markdown link. + * + * @param {String} `anchor` The string to slugify + * @return {String} + * @api public + */ + +utils.slugify = function(anchor) { + anchor = utils.stripColor(anchor); + anchor = anchor.toLowerCase(); + anchor = anchor.split(/ /).join('-'); + anchor = anchor.split(/\t/).join('--'); + anchor = anchor.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join(''); + return anchor; +}; + +utils.links = function(rules) { + if (rules._added) return; + rules._added = true; + var open = rules.link_open; + rules.link_open = function(tokens, idx, options, env) { + open.apply(rules, arguments); + var token = tokens[idx]; + if (/[.\\\/]+issues/.test(token.href)) { + return; + } + + if (options.paths && !/http/.test(token.href)) { + var href = token.href.replace(/^\.?[\\\/]+/, '').split(/[\\\/]+/).join('/'); + var paths = options.paths; + var anchors = paths.anchors; + var file = options.file; + if (/#/.test(href)) { + var idx = href.indexOf('#'); + var val = href.slice(idx + 1); + var pre = href.slice(0, idx); + var anc = anchors[pre]; + if (anc && anc.indexOf(val) === -1) { + console.log(pre) + throw new Error(`cannot find anchor: #${val} in "${pre}" (defined in ${file.path})`); + } + } else { + var segs = href.split('/'); + var len = segs.length; + if (len === 1) { + segs.unshift(file.dir); + } else if (len === 2 && segs[0] === '..') { + segs[0] = 'docs'; + } else if (len > 2 && segs[0] === '..') { + segs.shift(); + } + + var seg = segs.shift(); + var rest = segs.join('/'); + var group = paths[seg]; + + if (typeof group === 'undefined') { + throw new Error(`directory group: "${seg}" is not defined`); + } + if (group.indexOf(rest) === -1 && !/issues/.test(rest)) { + throw new Error(`cannot find filepath: "${rest}" in "${seg}" (${file.path})`); + } + } + } + return ''; + }; + return rules; +}; + +utils.lintLinks = function(options) { + utils.links(rules); + + return function(md) { + md.renderer.renderInline = function(tokens, options, env) { + var len = tokens.length, i = 0; + var str = ''; + + while (len--) { + str += rules[tokens[i].type](tokens, i++, options, env, this); + } + return str; + }; + + md.renderer.render = function(tokens, options, env) { + var len = tokens.length, i = -1; + var str = ''; + + while (++i < len) { + if (tokens[i].type === 'inline') { + str += this.renderInline(tokens[i].children, options, env); + } else { + str += rules[tokens[i].type](tokens, i, options, env, this); + } + } + return str; + }; + }; +}; diff --git a/support/logo.png b/support/logo.png new file mode 100755 index 0000000..3009a36 Binary files /dev/null and b/support/logo.png differ diff --git a/support/package.json b/support/package.json new file mode 100644 index 0000000..72babd7 --- /dev/null +++ b/support/package.json @@ -0,0 +1,52 @@ +{ + "name": "update-docs", + "description": "Documentation for Update.", + "version": "0.0.0", + "homepage": "https://github.com/update/update-docs", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "update/update-docs", + "bugs": { + "url": "https://github.com/update/update-docs/issues" + }, + "private": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "devDependencies": { + "base-generators": "^0.4.1", + "copy": "^0.2.3", + "delete": "^0.3.2", + "export-files": "^2.1.1", + "gulp-drafts": "^0.2.0", + "gulp-format-md": "^0.1.9", + "gulp-reflinks": "^0.1.0", + "has-value": "^0.3.1", + "is-valid-app": "^0.2.0", + "memoize-path": "^0.1.2", + "pascalcase": "^0.1.1", + "pretty-remarkable": "^0.3.6", + "remarkable": "^1.6.2", + "set-blocking": "^2.0.0", + "template-helpers": "^0.6.3", + "through2": "^2.0.1", + "verb-toc": "^0.2.3" + }, + "dependencies": { + "generate-collections": "^0.3.4", + "generate-defaults": "^0.3.1", + "lazy-cache": "^2.0.1", + "strip-color": "^0.1.0" + }, + "verb": { + "plugins": [ + "gulp-format-md" + ], + "lint": { + "reflinks": true + } + } +} diff --git a/support/verbfile.js b/support/verbfile.js new file mode 100644 index 0000000..e436c4c --- /dev/null +++ b/support/verbfile.js @@ -0,0 +1,38 @@ +'use strict'; + +var path = require('path'); +var copy = require('copy'); +var del = require('delete'); +var drafts = require('gulp-drafts'); +var reflinks = require('gulp-reflinks'); +var format = require('gulp-format-md'); +var paths = require('./lib/paths'); +var lib = require('./lib'); + +module.exports = function(app) { + var dest = paths.site(); + app.use(lib.middleware()); + app.use(lib.common()); + + app.task('clean', function(cb) { + del(paths.docs(), {force: true}, cb); + }); + + app.task('copy', function(cb) { + copy('*.png', paths.docs(), cb); + }); + + app.task('docs', ['clean', 'copy'], function(cb) { + app.layouts('docs/layouts/*.md', {cwd: paths.cwd()}); + app.docs('docs/*.md', {cwd: paths.cwd(), layout: 'default'}); + + return app.toStream('docs') + .pipe(drafts()) + .pipe(app.renderFile('*')) + .pipe(reflinks()) + .pipe(format()) + .pipe(app.dest(paths.docs())); + }); + + app.task('default', ['docs']); +}; diff --git a/test/_suite.js b/test/_suite.js new file mode 100644 index 0000000..8a136aa --- /dev/null +++ b/test/_suite.js @@ -0,0 +1,21 @@ +'use strict'; + +var generate = require('..'); +var runner = require('base-test-runner')(); +var suite = require('base-test-suite'); + +/** + * Run the tests in `base-test-suite` + */ + +runner.on('templates', function(file) { + var fn = require(file.path); + if (typeof fn === 'function') { + fn(generate); + } else { + throw new Error('expected ' + file.path + ' to export a function'); + } +}); + +runner.addFiles('templates', suite.test.templates); +runner.addFiles('templates', suite.test['assemble-core']); diff --git a/test/app.applyLayout.js b/test/app.applyLayout.js deleted file mode 100644 index 6fdf390..0000000 --- a/test/app.applyLayout.js +++ /dev/null @@ -1,85 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; -var page = { - content: '<%= name %>', - layout: 'default.tmpl', - locals: { - name: 'Halle' - } -}; - -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', {viewType: 'layout'}); - app.create('page'); - }); - - it('should throw an error when a layout cannot be found:', function (done) { - app.layout('fofof.tmpl', {content: '..'}); - app.page('a.tmpl', page) - .render(function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); - }); - }); - - it('should emit an error when a layout cannot be found:', function (done) { - app.layout('fofof.tmpl', {content: '..'}); - app.on('error', function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); - }); - - app.page('a.tmpl', page) - .render(function () { - }); - }); - - it('should throw an error - layout defined but no layouts registered:', function (done) { - app.page('a.tmpl', page) - .render(function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); - }); - }); - - it('should emit an error - layout defined but no layouts registered:', function (done) { - app.on('error', function (err) { - assert(err.message === 'Templates#layouts no layouts are registered, but one is defined: default.tmpl'); - done(); - }); - app.page('a.tmpl', page) - .render(function () { - }); - }); - - it('should wrap a view with a layout (view.render):', function (done) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page) - .render(function (err) { - if (err) return done(err); - done(); - }); - }); - - it('should wrap a view with a layout (app.render):', function (done) { - app.layout('default.tmpl', {content: 'before {% body %} after'}); - app.page('a.tmpl', page); - - var view = app.pages.getView('a.tmpl'); - app.render(view, function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'before Halle after'); - done(); - }); - }); - }); -}); - diff --git a/test/app.cli.js b/test/app.cli.js new file mode 100644 index 0000000..9b2227a --- /dev/null +++ b/test/app.cli.js @@ -0,0 +1,20 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var update = require('..'); +var app; + +describe('app.cli', function() { + beforeEach(function() { + app = update({cli: true}); + }); + + describe('app.cli.map', function() { + it('should add a property to app.cli', function() { + app.cli.map('abc', function() {}); + assert.equal(app.cli.keys.pop(), 'abc'); + }); + }); +}); + diff --git a/test/app.collection.compile.js b/test/app.collection.compile.js deleted file mode 100644 index 6bd595b..0000000 --- a/test/app.collection.compile.js +++ /dev/null @@ -1,50 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var views; - -describe('compile', function () { - beforeEach(function () { - views = new Views(); - }); - - it('should throw an error when an engine cannot be found:', function () { - views.addView('foo.bar', {content: '<%= name %>'}); - var page = views.getView('foo.bar'); - (function() { - views.compile(page); - }).should.throw('Views#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function () { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = views.getView('a.tmpl'); - var view = views.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function () { - views.engine('tmpl', require('engine-base')); - views.addView('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = views.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function () { - views.engine('md', require('engine-base')); - views.addView('foo.md', {content: '<%= name %>'}); - var page = views.getView('foo.md'); - (function() { - views.compile(page, function () {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - - (function() { - views.compile(page, {}, function () {}); - }).should.throw('Views#compile is sync and does not take a callback function'); - }); -}); diff --git a/test/app.collection.js b/test/app.collection.js deleted file mode 100644 index 1fc0aa4..0000000 --- a/test/app.collection.js +++ /dev/null @@ -1,180 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var define = require('define-property'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var app; - -describe('collection', function () { - describe('method', function () { - beforeEach(function () { - app = new App(); - }); - - it('should expose the collection method', function () { - assert(typeof app.collection === 'function'); - }); - - it('should return a new collection', function () { - var collection = app.collection(); - assert(typeof collection === 'object'); - }); - - it('should have isCollection property', function () { - var collection = app.collection(); - assert(collection.isCollection === true); - }); - }); - - describe('adding views', function () { - beforeEach(function () { - app = new App() - .use(function () { - return function () { - define(this, 'count', { - get: function() { - return Object.keys(this.views).length; - }, - set: function () { - throw new Error('count is a read-only getter and cannot be defined.'); - } - }); - }; - }); - - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should load a view onto the respective collection:', function () { - app.pages('test/fixtures/pages/a.hbs'); - app.views.pages.should.have.property(path.resolve('test/fixtures/pages/a.hbs')); - }); - - it('should allow collection methods to be chained:', function () { - app - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') - ]); - }); - - it('should expose the `option` method:', function () { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - app.pages.options.should.have.property('foo', 'bar'); - - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') - ]); - }); - - it('should expose the `option` method:', function () { - app.pages.option('foo', 'bar') - .pages('test/fixtures/pages/a.hbs') - .pages('test/fixtures/pages/b.hbs') - .pages('test/fixtures/pages/c.hbs'); - - assert(app.pages.count === 3); - }); - }); - - describe('addItem', function () { - beforeEach(function () { - app = new App(); - }); - - it('should add items to a collection', function () { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a collection from an existing collection:', function () { - var pages = app.collection({Collection: Collection}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.collection(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering views', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should render a view with inherited app.render', function (done) { - app.cache.data = {}; - - app.page('test/fixtures/templates/a.tmpl') - .use(function(view) { - view.contents = fs.readFileSync(view.path); - }) - .set('data.name', 'Brian') - .render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'Brian'); - done(); - }); - }); - }); -}); - -describe('collection singular method', function () { - describe('create', function () { - beforeEach(function () { - app = new App(); - }); - - it('should add a pluralized collection from singular name', function () { - app.create('page'); - assert(typeof app.views.pages === 'object'); - }); - }); - - describe('adding views', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should add a view to the created collection:', function () { - app.page('test/fixtures/pages/a.hbs'); - assert(typeof app.views.pages[path.resolve('test/fixtures/pages/a.hbs')] === 'object'); - }); - - it('should expose the `option` method:', function () { - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo', 'bar'); - }); - }); -}); diff --git a/test/app.collection.render.js b/test/app.collection.render.js deleted file mode 100644 index 135b134..0000000 --- a/test/app.collection.render.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages, app; - -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { - app = App(); - pages = app.create('pages'); - app.engine('tmpl', require('engine-base')); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function () { - (function() { - app.pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function (done) { - pages.addView('foo.bar', {content: '<%= name %>'}); - var page = pages.getView('foo.bar'); - - app.pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers defined on app to render a view:', function (done) { - var locals = {name: 'Halle'}; - app.helper('upper', function (str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - app.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers defined on app to render a view with collection.render:', function (done) { - var locals = {name: 'Halle'}; - app.helper('upper', function (str) { - return str.toUpperCase(str) + 'app'; - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - pages.helper('upper', app._.helpers.sync.upper); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLEapp b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function (done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function (done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, - }); - - pages.use(function (collection) { - collection.option('pager', false); - - collection.renderEach = function (cb) { - var list = new List(collection); - async.map(list.items, function (item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function (err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/app.compile.js b/test/app.compile.js deleted file mode 100644 index 423c9f9..0000000 --- a/test/app.compile.js +++ /dev/null @@ -1,52 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('compile', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should throw an error when an engine cannot be found:', function () { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - (function() { - app.compile(page); - }).should.throw('Templates#compile cannot find an engine for: .bar'); - }); - - it('should compile a template:', function () { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var page = app.pages.getView('a.tmpl'); - var view = app.compile(page); - assert.equal(typeof view.fn, 'function'); - }); - - it('should compile a template by name:', function () { - app.engine('tmpl', require('engine-base')); - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', a: 'b'}); - - var view = app.compile('a.tmpl'); - assert.equal(typeof view.fn, 'function'); - }); - - it('should throw an error when a callback is given:', function () { - app.engine('md', require('engine-base')); - app.page('foo.md', {content: '<%= name %>'}); - var page = app.pages.getView('foo.md'); - (function() { - app.compile(page, function () { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - - (function() { - app.compile(page, {}, function () { - }); - }).should.throw('Templates#compile is sync and does not take a callback function'); - }); -}); diff --git a/test/app.copy.js b/test/app.copy.js deleted file mode 100644 index f3d3ceb..0000000 --- a/test/app.copy.js +++ /dev/null @@ -1,31 +0,0 @@ -require('mocha'); -var path = require('path'); -var assert = require('assert'); -var rimraf = require('rimraf'); -var App = require('..'); -var app; - -var fixtures = path.join(__dirname, 'fixtures/copy/*.txt'); -var actual = path.join(__dirname, 'actual'); - -describe('copy()', function() { - beforeEach(function (cb) { - rimraf(actual, cb); - app = new App(); - }); - - afterEach(function (cb) { - rimraf(actual, cb); - }); - - describe('streams', function () { - it('should copy files', function (cb) { - app.copy(fixtures, path.join(__dirname, 'actual')) - .on('error', cb) - .on('data', function (file) { - assert.equal(typeof file, 'object'); - }) - .on('end', cb); - }); - }); -}); diff --git a/test/app.create.js b/test/app.create.js deleted file mode 100644 index 9178245..0000000 --- a/test/app.create.js +++ /dev/null @@ -1,190 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('create', function () { - describe('inflections', function () { - beforeEach(function () { - app = new App(); - }); - - it('should expose the create method', function () { - assert(typeof app.create === 'function'); - }); - - it('should add a collection to `views`', function () { - app.create('pages'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.pages === 'function'); - }); - - it('should add a pluralized collection to `views`', function () { - app.create('page'); - assert(typeof app.views.pages === 'object'); - assert(typeof app.page === 'function'); - }); - }); - - describe('custom constructors', function () { - beforeEach(function () { - var Vinyl = require('vinyl'); - Vinyl.prototype.custom = function (key) { - this[key] = 'nonsense'; - return this; - }; - app = new App({View: Vinyl}); - app.create('pages'); - }); - - it('should create views from key-value pairs:', function () { - app.page('a.hbs', {path: 'a.hbs', content: 'a'}); - app.page('b.hbs', {path: 'b.hbs', content: 'b'}); - app.page('c.hbs', {path: 'c.hbs', content: 'c'}); - var a = app.pages.getView('a.hbs'); - a.custom('foo'); - a.foo.should.equal('nonsense'); - }); - }); - - describe('custom instances', function () { - it('should create views from custom `View` and `Views` instance/ctor:', function () { - var Vinyl = require('vinyl'); - Vinyl.prototype.read = function (file) { - return fs.readFileSync(file.path); - }; - - var Views = App.Views; - var views = new Views({View: Vinyl}); - - views.addView('a.hbs', {path: 'a.hbs', content: 'a'}); - views.addView('b.hbs', {path: 'b.hbs', content: 'b'}); - views.addView('c.hbs', {path: 'c.hbs', content: 'c'}); - - app = new App(); - app.create('pages', views); - - var a = app.pages.getView('a.hbs'); - assert(a instanceof Vinyl); - assert(Vinyl.isVinyl(a)); - assert(typeof a.read === 'function'); - - views.addView('d.hbs', {path: 'd.hbs', content: 'd'}); - var d = app.pages.getView('d.hbs'); - assert(d instanceof Vinyl); - assert(Vinyl.isVinyl(d)); - }); - }); - - describe('chaining', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should create views from key-value pairs:', function () { - app.page('a.hbs', {content: 'a'}); - app.page('b.hbs', {content: 'b'}); - app.page('c.hbs', {content: 'c'}); - app.views.pages.should.have.properties(['a.hbs', 'b.hbs', 'c.hbs']); - assert(app.views.pages['a.hbs'].content === 'a'); - }); - - it('should create views from file paths:', function () { - app.page('test/fixtures/pages/a.hbs'); - app.page('test/fixtures/pages/b.hbs'); - app.page('test/fixtures/pages/c.hbs'); - - app.views.pages.should.have.properties([ - path.resolve('test/fixtures/pages/a.hbs'), - path.resolve('test/fixtures/pages/b.hbs'), - path.resolve('test/fixtures/pages/c.hbs') - ]); - }); - }); - - - describe('instance', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should return the collection instance', function () { - var collection = app.create('pages'); - assert(collection instanceof App.Views); - - collection.option('renameKey', function (key) { - return path.basename(key); - }); - collection - .use(function (views) { - views.read = function (name) { - var view = this.getView(name); - if (!view.contents) { - view.contents = fs.readFileSync(view.path); - } - }; - }); - - collection.addView('test/fixtures/templates/a.tmpl'); - collection.read('a.tmpl'); - assert(collection.getView('a.tmpl').content === '<%= name %>'); - }); - }); - - describe('viewType', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection to the given viewType', function () { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should add a collection to multiple viewTypes', function () { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - }); - - describe('events', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should emit `create` when a collection is created:', function () { - app.on('create', function (collection) { - if (collection.options.plural === 'layouts') { - collection.options.foo = 'bar'; - } - }); - - app.create('layout'); - app.layout('one', {path: 'two', contents: '...'}); - assert(app.layouts.options.foo === 'bar'); - }); - }); - - describe('collection instantiation', function () { - it('should expose collection instance methods that are created after instantiation on the app collection loader', function () { - app.create('pages'); - app.pages.use(function (collection) { - collection.define('foo', function (msg) { - return 'foo ' + msg; - }); - }); - - assert(app.pages.foo); - assert(typeof app.pages.foo === 'function'); - }); - }); -}); diff --git a/test/app.data.js b/test/app.data.js deleted file mode 100644 index 9cf2cd3..0000000 --- a/test/app.data.js +++ /dev/null @@ -1,94 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.data', function () { - beforeEach(function () { - app = new App(); - }); - - it('should set a key-value pair on cache.data:', function () { - app.data('a', 'b'); - assert(app.cache.data.a === 'b'); - }); - - it('should set an object on cache.data:', function () { - app.data({c: 'd'}); - assert(app.cache.data.c === 'd'); - }); - - it('should load data from a file onto cache.data:', function () { - app.data('test/fixtures/data/a.json'); - assert(app.cache.data.a.one.a === 'aaa'); - }); - - it('should load a glob of data onto cache.data:', function () { - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.a.one.a === 'aaa'); - assert(app.cache.data.b.two.b === 'bbb'); - assert(app.cache.data.c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on global opts:', function () { - app.option('namespace', function (key) { - return 'prefix_' + path.basename(key, path.extname(key)); - }); - app.data('test/fixtures/data/*.json'); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `namespace` defined on data opts:', function () { - app.data('test/fixtures/data/*.json', { - namespace: function (key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should use `renameKey` defined on data opts:', function () { - app.data('test/fixtures/data/*.json', { - renameKey: function (key) { - return 'prefix_' + path.basename(key, path.extname(key)); - } - }); - assert(app.cache.data.prefix_a.one.a === 'aaa'); - assert(app.cache.data.prefix_b.two.b === 'bbb'); - assert(app.cache.data.prefix_c.three.c === 'ccc'); - }); - - it('should extend `cache.data`', function() { - app.data({a: 'aaa', b: 'bbb', c: 'ccc'}); - app.data({x: 'xxx', y: 'yyy', z: 'zzz'}); - assert(app.cache.data.a === 'aaa'); - assert(app.cache.data.b === 'bbb'); - assert(app.cache.data.c === 'ccc'); - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.y === 'yyy'); - assert(app.cache.data.z === 'zzz'); - }); - - it('should extend the `cache.data` object when the first param is a string.', function() { - app.data('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.data('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - assert(app.cache.data.foo.x === 'xxx'); - assert(app.cache.data.bar.a === 'aaa'); - }); - - it('should be chainable.', function() { - app - .data({x: 'xxx', y: 'yyy', z: 'zzz'}) - .data({a: 'aaa', b: 'bbb', c: 'ccc'}); - - assert(app.cache.data.x === 'xxx'); - assert(app.cache.data.a === 'aaa'); - }); -}); diff --git a/test/app.dest.js b/test/app.dest.js deleted file mode 100644 index 1e3d67e..0000000 --- a/test/app.dest.js +++ /dev/null @@ -1,1103 +0,0 @@ -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; - -require('mocha'); -var should = require('should'); -var assert = require('assert'); -var App = require('..'); -var app; - -var path = require('path'); -var fs = require('graceful-fs'); -var rimraf = require('rimraf'); - -var bufferStream; -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); - -var actual = path.join(__dirname, 'actual'); - -var wipeOut = function(cb) { - app = new App(); - rimraf(path.join(__dirname, 'actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('dest stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should explode on invalid folder (empty)', function(done) { - var stream; - try { - stream = app.dest(); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should explode on invalid folder (empty string)', function(done) { - var stream; - try { - stream = app.dest(''); - } catch (err) { - assert(err && typeof err === 'object'); - should.not.exist(stream); - done(); - } - }); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.dest(path.join(__dirname, 'actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not write null files', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(false); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - done(); - }; - - var stream = app.dest(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test'); - var expectedCwd = __dirname; - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(expectedCwd, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - fs.lstatSync(expectedPath).isDirectory().should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should allow piping multiple dests in streaming mode', function(done) { - var inputPath1 = path.join(__dirname, 'actual/multiple-first'); - var inputPath2 = path.join(__dirname, 'actual/multiple-second'); - var inputBase = path.join(__dirname, 'actual/'); - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream1 = app.dest('./actual/', {cwd: __dirname}); - var stream2 = app.dest('./actual/', {cwd: __dirname}); - var content = fs.readFileSync(srcPath); - var rename = through.obj(function(file, _, next) { - file.path = inputPath2; - this.push(file); - next(); - }); - - stream1.on('data', function(file) { - file.path.should.equal(inputPath1); - }); - - stream1.pipe(rename).pipe(stream2); - stream2.on('data', function(file) { - file.path.should.equal(inputPath2); - }).once('end', function() { - fs.readFileSync(inputPath1, 'utf8').should.equal(content.toString()); - fs.readFileSync(inputPath2, 'utf8').should.equal(content.toString()); - done(); - }); - - var file = new File({ - base: inputBase, - path: inputPath1, - cwd: __dirname, - contents: content - }); - - stream1.write(file); - stream1.end(); - }); - - it('should write new files with the default user mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0666 & (~process.umask()); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write new files with the specified mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedMode = 0744; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname, mode:expectedMode}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should update file mode to match the vinyl mode', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var startMode = 0655; - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - assert(chmodSpy.called); - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - fs.existsSync(expectedPath).should.equal(true); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, startMode); - - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - var expectedBase = path.join(__dirname, 'actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as string', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base as function', function(done) { - var inputBase = path.join(__dirname, 'fixtures/vinyl'); - var inputPath = path.join(__dirname, 'fixtures/vinyl/wow/suchempty'); - - var firstFile = new File({ - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function() { - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.dest('./actual/', { - cwd: __dirname, - base: function(file){ - should.exist(file); - file.path.should.equal(inputPath); - return inputBase; - } - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, 0); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report stat errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - return new Error('stat error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('stat error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should report chmod errors', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - - spies.setError(function(mod, fn) { - if (fn === 'chmod' && arguments[2] === expectedPath) { - return new Error('chmod error'); - } - }); - - var stream = app.dest('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.message.should.equal('chmod error'); - done(); - }); - stream.write(expectedFile); - }); - - it('should not chmod a matching file', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - realMode(fs.lstatSync(expectedPath).mode).should.equal(expectedMode); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should see a file with special chmod (setuid/setgid/sticky) as matching', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, 'actual'); - var expectedMode = 03722; - var normalMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: normalMode - } - }); - - var expectedCount = 0; - spies.setError(function(mod, fn) { - if (fn === 'stat' && arguments[2] === expectedPath) { - expectedCount++; - } - }); - - var onEnd = function(){ - expectedCount.should.equal(1); - assert(!chmodSpy.called); - done(); - }; - - fs.mkdirSync(expectedBase); - fs.closeSync(fs.openSync(expectedPath, 'w')); - fs.chmodSync(expectedPath, expectedMode); - - statSpy.reset(); - chmodSpy.reset(); - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should not overwrite files with overwrite option set to false', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(existingContents)).should.equal(true); - done(); - }; - - // Write expected file which should not be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: false}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should overwrite files with overwrite option set to true', function(done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputContents = fs.readFileSync(inputPath); - - var expectedPath = path.join(__dirname, 'actual/test.coffee'); - var expectedBase = path.join(__dirname, 'actual'); - var existingContents = 'Lorem Ipsum'; - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: inputContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - bufEqual(fs.readFileSync(expectedPath), new Buffer(inputContents)).should.equal(true); - done(); - }; - - // This should be overwritten - fs.mkdirSync(expectedBase); - fs.writeFileSync(expectedPath, existingContents); - - var stream = app.dest('./actual/', {cwd: __dirname, overwrite: true}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should create symlinks when the `symlink` attribute is set on the file', function (done) { - var inputPath = path.join(__dirname, 'fixtures/vinyl/test-create-dir-symlink'); - var inputBase = path.join(__dirname, 'fixtures/vinyl/'); - var inputRelativeSymlinkPath = 'wow'; - - var expectedPath = path.join(__dirname, 'actual/test-create-dir-symlink'); - - var inputFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, //'' - }); - - // `src()` adds this side-effect with `keepSymlinks` option set to false - inputFile.symlink = inputRelativeSymlinkPath; - - var onEnd = function(){ - fs.readlink(buffered[0].path, function () { - buffered[0].symlink.should.equal(inputFile.symlink); - buffered[0].path.should.equal(expectedPath); - done(); - }); - }; - - var stream = app.dest('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(inputFile); - stream.end(); - }); - - it('should emit finish event', function(done) { - var srcPath = path.join(__dirname, 'fixtures/vinyl/test.coffee'); - var stream = app.dest('./actual/', {cwd: __dirname}); - - stream.once('finish', function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); -}); - -describe('dest', function() { - beforeEach(function (done) { - rimraf(actual, done); - app = new App(); - }); - - afterEach(function (done) { - rimraf(actual, done); - }); - - describe('streams', function () { - it('should return a stream', function (done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function (file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function (file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt'), {buffer: false}); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function (file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that writes streaming files to new directories', function (done) { - testWriteDir({}, done); - }); - - it('should return an output stream that writes streaming files to new directories (buffer: false)', function (done) { - testWriteDir({buffer: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false)', function (done) { - testWriteDir({read: false}, done); - }); - - it('should return an output stream that writes streaming files to new directories (read: false, buffer: false)', function (done) { - testWriteDir({buffer: false, read: false}, done); - }); - - }); - - describe('ext', function () { - beforeEach(function () { - app = new App(); - app.set('ext', '.txt'); - }); - - afterEach(function () { - app.set('ext', '.html'); - }); - - it('should return a stream', function (done) { - var stream = app.dest(path.join(__dirname, 'fixtures/')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an output stream that writes files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/copy/e*.txt')); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function (file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - String(file.contents).should.equal('Hello world!'); - }); - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { - should.not.exist(err); - should.exist(contents); - String(contents).should.equal('Hello world!'); - done(); - }); - }); - }); - - it('should return an output stream that does not write non-read files', function (done) { - var instream = app.src(path.join(__dirname, 'fixtures/dest/*.txt'), {read: false}); - var outstream = app.dest(actual); - instream.pipe(outstream); - - outstream.on('error', done); - outstream.on('data', function (file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - path.join(file.path, '').should.equal(path.join(actual, 'example.txt')); - }); - - outstream.on('end', function () { - fs.readFile(path.join(actual, 'example.txt'), function (err, contents) { - should.exist(err); - should.not.exist(contents); - done(); - }); - }); - }); - }); - - function testWriteDir(srcOptions, done) { - var instream = app.src(path.join(__dirname, 'fixtures/generic'), srcOptions); - var outstream = instream.pipe(app.dest(actual)); - - outstream.on('error', done); - outstream.on('data', function(file) { - // data should be re-emitted correctly - should.exist(file); - should.exist(file.path); - path.join(file.path,'').should.equal(path.join(actual, 'generic')); - }); - - outstream.on('end', function() { - fs.exists(path.join(actual, 'generic'), function(exists) { - /* jshint expr: true */ - should(exists).be.ok; - /* jshint expr: false */ - done(); - }); - }); - } -}); - diff --git a/test/app.doc.js b/test/app.doc.js new file mode 100644 index 0000000..abf0879 --- /dev/null +++ b/test/app.doc.js @@ -0,0 +1,23 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add doc', function() { + it('should add docs to `app.views.docs`:', function() { + app.doc('a.hbs', {path: 'a.hbs', content: 'a'}); + app.doc('b.hbs', {path: 'b.hbs', content: 'b'}); + app.doc('c.hbs', {path: 'c.hbs', content: 'c'}); + assert(Object.keys(app.views.docs).length === 3); + }); + }); +}); diff --git a/test/app.docs.js b/test/app.docs.js new file mode 100644 index 0000000..cdf8955 --- /dev/null +++ b/test/app.docs.js @@ -0,0 +1,25 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add docs', function() { + it('should add docs to `app.views.docs`:', function() { + app.docs({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'}, + }); + assert.equal(Object.keys(app.views.docs).length, 3); + }); + }); +}); diff --git a/test/app.engines.js b/test/app.engines.js deleted file mode 100644 index 17c0669..0000000 --- a/test/app.engines.js +++ /dev/null @@ -1,159 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('engine support', function() { - beforeEach(function() { - app = new App(); - }); - - it('should throw an error when engine name is invalid:', function () { - (function () { - app.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function () { - app.engine('hbs', function () {}); - assert(typeof app.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function () { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - app.engine('hbs', hbs); - assert(app.engines['.hbs']); - assert(app.engines['.hbs'].renderFile); - assert(app.engines['.hbs'].render); - }); - - it('should get an engine:', function () { - app.engine('hbs', function () {}); - var hbs = app.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should return undefined if no engine is found:', function () { - var hbs = app.getEngine(); - assert.equal(typeof hbs, 'undefined'); - }); - - it('should register multiple engines to the given extension', function () { - app.engine(['hbs', 'md'], function () {}); - assert(typeof app.engines['.hbs'] === 'object'); - assert(typeof app.engines['.md'] === 'object'); - }); -}); - -describe('engines', function () { - beforeEach(function () { - app = new App(); - app.create('pages'); - app.pages('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - app.pages('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function () { - app.engine('a', {render: function () {}}); - app.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function (done) { - app.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - app.render('foo.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should override individual delims values:', function (done) { - app.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - app.render('bar.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); - res.contents.toString().should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should get an engine:', function () { - app.engine('a', { - render: function () {} - }); - var a = app.engine('a'); - a.should.have.property('render'); - }); -}); - - -describe('engine selection:', function () { - beforeEach(function (done) { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - app.create('pages'); - done(); - }); - - it('should get the engine from file extension:', function (done) { - app.page('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the collection:', function (done) { - app.create('posts', {engine: 'hbs'}); - - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the view:', function (done) { - app.create('posts'); - app.post('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on `view.data`:', function (done) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on render locals:', function (done) { - app.create('posts'); - app.post('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); -}); diff --git a/test/app.events.js b/test/app.events.js deleted file mode 100644 index 6be9215..0000000 --- a/test/app.events.js +++ /dev/null @@ -1,49 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('events', function () { - beforeEach(function () { - app = new App(); - }); - - it('should listen for an event:', function () { - var app = new App(); - app.on('foo', function () {}); - assert(Array.isArray(app._callbacks['$foo'])); - }); - - it('should emit an event:', function (done) { - var app = new App(); - app.on('foo', function (val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - - it('should listen for error events:', function(done) { - var app = new App(); - app.on('foo', function(val) { - assert(val === 'bar'); - done(); - }); - assert(Array.isArray(app._callbacks['$foo'])); - app.emit('foo', 'bar'); - }); - - it('should listen for `view` events:', function () { - var app = new App(); - - app.on('view', function (view) { - view.foo = 'bar'; - }); - - var view = app.view({path: 'a', content: 'b'}); - assert(view.foo === 'bar'); - }); -}); diff --git a/test/app.extendWith.js b/test/app.extendWith.js new file mode 100644 index 0000000..7228e75 --- /dev/null +++ b/test/app.extendWith.js @@ -0,0 +1,599 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var gm = require('global-modules'); +var npm = require('npm-install-global'); +var isAbsolute = require('is-absolute'); +var exists = require('fs-exists-sync'); +var resolve = require('resolve'); +var Base = require('..'); +var base; + +var fixture = path.resolve.bind(path, __dirname, 'fixtures/updaters'); +function resolver(search, app) { + try { + if (isAbsolute(search.name)) { + search.name = require.resolve(search.name); + } else { + search.name = resolve.sync(search.name, {basedir: gm}); + } + search.app = app.register(search.name, search.name); + } catch (err) {} +} + +describe('.extendWith', function() { + before(function(cb) { + if (!exists(path.resolve(gm, 'generate-bar'))) { + npm.install('generate-bar', cb); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + base.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + base.on('unresolved', resolver); + }); + + it.skip('should throw an error when an updater is not found', function(cb) { + try { + base.register('foo', function(app) { + app.extendWith('fofoofofofofof'); + }); + + base.getUpdater('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot find updater: "fofoofofofofof"'); + cb(); + } + }); + + it('should extend an updater with settings in the default updater', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.options.foo, 'bar'); + assert.equal(app.cache.data.foo, 'bar'); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.data({foo: 'bar'}); + app.option({foo: 'bar'}); + }); + + base.update('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not extend tasks by default', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert(app.tasks.hasOwnProperty('default')); + assert(!app.tasks.hasOwnProperty('a')); + assert(!app.tasks.hasOwnProperty('b')); + assert(!app.tasks.hasOwnProperty('c')); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.task('a', function(next) { + next(); + }); + app.task('b', function(next) { + next(); + }); + app.task('c', function(next) { + next(); + }); + }); + + base.update('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should get a named updater', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.extendWith('bar'); + count++; + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + assert.equal(count, 1); + cb(); + }); + + it('should extend an updater with a named updater', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should extend an updater with an array of updaters', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(['bar', 'baz', 'qux']); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + }); + + base.register('baz', function(app) { + app.task('b', function() {}); + }); + + base.register('qux', function(app) { + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + describe('invoke updaters', function(cb) { + it('should extend with an updater instance', function(cb) { + base.register('foo', function(app) { + var bar = app.getUpdater('bar'); + app.extendWith(bar); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.isBar = true; + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a named updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + }); + + describe('extend updaters', function(cb) { + it('should extend an updater with an updater invoked by name', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should extend an updater with an updater invoked by alias', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('qux'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('generate-qux', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('qux'); + base.getUpdater('foo'); + }); + + it('should extend with an updater invoked by filepath', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(fixture('qux')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should extend with an updater invoked from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('abc'); + }); + + it('should extend with an updater invoked from node_modules by name on a default instance', function() { + var app = new Base(); + + app.on('unresolved', resolver); + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should use an updater from node_modules as a plugin', function() { + var app = new Base(); + + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.use(require('generate-foo')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should extend with an updater invoked from global modules by name', function(cb) { + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + app.extendWith('generate-bar'); + + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('zzz'); + }); + + it('should extend with an updater invoked from global modules by alias', function(cb) { + base.register('generate-bar'); + + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getUpdater('zzz'); + }); + }); + + describe('sub-updaters', function(cb) { + it('should invoke sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith('one'); + app.extendWith('two'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a sub-updater on the base instance', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + + it('should invoke a sub-updater from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('generate-foo'); + }); + + base.getUpdater('abc'); + }); + + it('should invoke a sub-updater from node_modules by alias', function(cb) { + base.register('generate-foo'); + + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('foo'); + }); + + base.getUpdater('abc'); + }); + + it('should invoke an array of sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith(['one', 'two']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updaters from sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith('one.a'); + app.extendWith('two.a'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke an array of sub-updaters from sub-updaters', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith(['one.a', 'two.a']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.extendWith('baz'); + }); + + base.register('baz', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another sub-updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + + it('should invoke sub-updater that invokes another sub-updater', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getUpdater('foo'); + }); + }); +}); diff --git a/test/app.get-set.js b/test/app.get-set.js deleted file mode 100644 index 8bee52c..0000000 --- a/test/app.get-set.js +++ /dev/null @@ -1,72 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.set()', function () { - beforeEach(function() { - app = new App(); - }); - - it('should set a value', function () { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); - - it('should set properties on the instance.', function () { - app.set('a', 'b'); - app.a.should.equal('b'); - }); - - it('should allow an object to be set directly.', function () { - app.set({x: 'y'}); - app.x.should.equal('y'); - app.get('x').should.equal('y'); - }); - - it('should set nested properties on the instance.', function () { - app.set('c', {d: 'e'}); - app.get('c').d.should.equal('e'); - }); - - it('should use dot notation to `set` values.', function () { - app.set('h.i', 'j'); - app.get('h').should.eql({i: 'j'}); - }); - - it('should use dot notation to `get` values.', function () { - app.set('h', {i: 'j'}); - app.get('h.i').should.equal('j'); - }); - - it('should return `this` for chaining', function () { - app.set('a', 'b').should.equal(app); - app - .set('aa', 'bb') - .set('bb', 'cc') - .set('cc', 'dd'); - app.get('aa').should.equal('bb'); - app.get('bb').should.equal('cc'); - app.get('cc').should.equal('dd'); - }); - - it('should return undefined when not set', function () { - app.set('a', undefined).should.equal(app); - }); -}); - -describe('app.get()', function () { - beforeEach(function() { - app = new App(); - }); - - it('should return undefined when no set', function () { - assert(app.get('a') === undefined); - }); - - it('should otherwise return the value', function () { - app.set('a', 'b'); - app.get('a').should.equal('b'); - }); -}); diff --git a/test/app.getUpdater.js b/test/app.getUpdater.js new file mode 100644 index 0000000..0b57e42 --- /dev/null +++ b/test/app.getUpdater.js @@ -0,0 +1,103 @@ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.updater', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should get a updater from the base instance', function() { + base.register('abc', function() {}); + var updater = base.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + + it('should fail when a updater is not found', function() { + var updater = base.getUpdater('whatever'); + assert(!updater); + }); + + it('should get a updater from the base instance from a nested updater', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = base.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a updater from the base instance using `this`', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = this.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a base updater from "app" from a nested updater', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var updater = app.getUpdater('abc'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'abc'); + }); + }); + base.getUpdater('xyz'); + }); + + it('should get a nested updater', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) {}); + }); + + var updater = base.getUpdater('abc.def'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'def'); + }); + + it('should get a deeply nested updater', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) { + def.register('ghi', function(ghi) { + ghi.register('jkl', function(jkl) { + jkl.register('mno', function() {}); + }); + }); + }); + }); + + var updater = base.getUpdater('abc.def.ghi.jkl.mno'); + assert(updater); + assert.equal(typeof updater, 'object'); + assert.equal(updater.name, 'mno'); + }); + + it('should get a updater that was registered by path', function() { + base.register('a', fixtures('updaters/a')); + var updater = base.getUpdater('a'); + + assert(updater); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('default')); + }); +}); diff --git a/test/app.handle.js b/test/app.handle.js deleted file mode 100644 index 8ce9902..0000000 --- a/test/app.handle.js +++ /dev/null @@ -1,33 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('handler', function () { - beforeEach(function () { - app = new App(); - app.create('pages'); - app.handlers(['foo']); - }); - - it('should support custom handle methods:', function (done) { - var page = app.page('foo', {contents: null}); - - app.handle('foo', page, function (err, view) { - assert(typeof view.path === 'string'); - done(); - }); - }); - - it('should not blow up if `options.handled` does not exist:', function (done) { - var page = app.page('foo', {contents: null}); - delete page.options.handled; - - app.handle('foo', page, function (err, view) { - assert(typeof view.path === 'string'); - done(); - }); - }); -}); diff --git a/test/app.handlers.js b/test/app.handlers.js deleted file mode 100644 index 71712f6..0000000 --- a/test/app.handlers.js +++ /dev/null @@ -1,72 +0,0 @@ -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function decorateViews(views) { - var fn = views.decorateView; - views.decorateView = function () { - var view = fn.apply(fn, arguments); - view.read = function () { - if (!this.contents) { - this.contents = fs.readFileSync(this.path); - } - }; - return view; - }; - views.loader = function (pattern) { - var files = resolve.sync(pattern); - return files.reduce(function (acc, fp) { - acc[fp] = {path: fp}; - return acc; - }, {}); - }; - return views; -} - -describe('handlers', function () { - describe('custom handlers', function () { - beforeEach(function () { - app = new App(); - app.create('pages') - .use(decorateViews) - .option('renameKey', function (key) { - return path.basename(key); - }); - }); - - it('should add custom middleware handlers:', function () { - app.handler('foo'); - app.router.should.have.property('foo'); - assert.equal(typeof app.router.foo, 'function'); - }); - - it('should add custom middleware handlers:', function () { - app.handler('foo'); - app.handler('bar'); - - app.foo(/./, function (view, next) { - view.one = 'aaa'; - next(); - }); - - app.bar(/./, function (view, next) { - view.two = 'zzz'; - next(); - }); - - app.page('abc', {content: '...'}) - .use(function (view) { - app.handleView('foo', view); - app.handleView('bar', view); - }); - - app.views.pages.abc.should.have.property('one', 'aaa'); - app.views.pages.abc.should.have.property('two', 'zzz'); - }); - }); -}); diff --git a/test/app.include.js b/test/app.include.js new file mode 100644 index 0000000..df7a101 --- /dev/null +++ b/test/app.include.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }); + + describe('add include', function() { + it('should add includes to `app.views.includes`:', function() { + app.include('a.hbs', {path: 'a.hbs', content: 'a'}); + app.include('b.hbs', {path: 'b.hbs', content: 'b'}); + app.include('c.hbs', {path: 'c.hbs', content: 'c'}); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.includes.js b/test/app.includes.js new file mode 100644 index 0000000..390660d --- /dev/null +++ b/test/app.includes.js @@ -0,0 +1,28 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }) + + describe('add includes', function() { + it('should add includes to `app.views.includes`:', function() { + app.includes({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'}, + }); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.js b/test/app.js deleted file mode 100644 index ef8e463..0000000 --- a/test/app.js +++ /dev/null @@ -1,130 +0,0 @@ -/* deps: coveralls istanbul */ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Base = App.Base; -var app; - -describe('app', function () { - describe('constructor', function () { - it('should create an instance of App:', function () { - app = new App(); - assert(app instanceof App); - }); - - it('should new up without new:', function () { - app = App(); - assert(app instanceof App); - }); - }); - - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof App.extend ==='function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - app = new App(); - }); - - it('should expose `set`', function () { - assert(typeof app.set ==='function'); - }); - it('should expose `get`', function () { - assert(typeof app.get ==='function'); - }); - it('should expose `visit`', function () { - assert(typeof app.visit ==='function'); - }); - it('should expose `define`', function () { - assert(typeof app.define ==='function'); - }); - it('should expose `views`', function () { - assert(typeof app.views === 'object'); - }); - }); - - describe('instance', function () { - beforeEach(function() { - app = new App(); - }); - - it('should set a value on the instance:', function () { - app.set('a', 'b'); - assert(app.a ==='b'); - }); - - it('should get a value from the instance:', function () { - app.set('a', 'b'); - assert(app.get('a') ==='b'); - }); - }); - - describe('initialization', function () { - it('should listen for errors:', function (done) { - app = new App(); - app.on('error', function (err) { - assert(err.message === 'foo'); - done(); - }); - app.emit('error', new Error('foo')); - }); - - it('should mixin methods after init:', function () { - app = new App(); - app.option({ - mixins: { - foo: function () {} - } - }); - assert(typeof app.foo ==='function'); - }); - - it('should expose constructors from `lib`:', function () { - app = new App(); - app.expose('Collection'); - assert(typeof app.Collection ==='function'); - }); - - it('should update constructors after init:', function () { - var Group = App.Group; - function MyGroup() { - Base.call(this); - } - Base.extend(MyGroup); - - app = new App(); - assert.equal(app.Group, Group); - assert.equal(app.get('Group'), Group); - app.option('Group', MyGroup); - assert.equal(app.Group, MyGroup); - assert.equal(app.get('Group'), MyGroup); - }); - - it('should mixin prototype methods defined on options:', function () { - app = new App({ - mixins: { - foo: function () {} - } - }); - assert(typeof app.foo ==='function'); - delete App.prototype.foo; - }); - - it('should expose `_` on app:', function () { - app = new App(); - assert(typeof app._ ==='object'); - }); - - it('should not re-add `_` in init:', function () { - app = new App(); - app._.foo = 'bar'; - app.defaultConfig(); - assert(app._.foo ==='bar'); - }); - }); -}); diff --git a/test/app.layout.js b/test/app.layout.js new file mode 100644 index 0000000..f763157 --- /dev/null +++ b/test/app.layout.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layout()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layout', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layout('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.layout('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.layout('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.layouts.js b/test/app.layouts.js new file mode 100644 index 0000000..d731a0b --- /dev/null +++ b/test/app.layouts.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layouts()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layouts', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layouts({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.list.compile.js b/test/app.list.compile.js deleted file mode 100644 index a49f81c..0000000 --- a/test/app.list.compile.js +++ /dev/null @@ -1,44 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var list; - -describe('app.list.compile', function () { - beforeEach(function () { - list = new List(); - list.engine('tmpl', require('engine-base')); - }); - - it('should compile an item:', function () { - var buffer = new Buffer('a b c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(typeof item.fn === 'function'); - }); - - it('should use the compiled function to render:', function () { - var buffer = new Buffer('a <%= title %> c'); - var item = list.addItem('a.tmpl', {contents: buffer}) - .compile(); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); - - it('should compile a view by name:', function () { - var buffer = new Buffer('a <%= title %> c'); - list.addItem('a.tmpl', {contents: buffer}); - - var item = list.compile('a.tmpl'); - - assert(item.fn({title: 'z'})); - assert(typeof item.fn({title: 'z'}) === 'string'); - assert(item.fn({title: 'z'}) === 'a z c'); - }); -}); - diff --git a/test/app.list.js b/test/app.list.js deleted file mode 100644 index 74b3dbe..0000000 --- a/test/app.list.js +++ /dev/null @@ -1,107 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var app; - -describe('list', function () { - describe('method', function () { - beforeEach(function () { - app = new App(); - }); - - it('should expose the list method', function () { - assert(typeof app.list === 'function'); - }); - - it('should return a new list', function () { - var list = app.list(); - assert(typeof list === 'object'); - }); - - it('should have isList property', function () { - var list = app.list(); - assert(list.isList === true); - }); - }); - - describe('adding items', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should add an item to a list:', function () { - app.pages('test/fixtures/pages/a.hbs'); - var list = app.list(); - list.addItem(app.pages.getView('test/fixtures/pages/a.hbs')); - assert(list.hasItem(path.resolve('test/fixtures/pages/a.hbs'))); - }); - - it('should expose the `option` method from a list:', function () { - var list = app.list(); - list.option('a', 'b'); - assert(list.options); - assert(list.options.a === 'b'); - }); - }); - - describe('addItem', function () { - beforeEach(function () { - app = new App(); - }); - - it('should add items to a list', function () { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - pages.items.hasOwnProperty('foo'); - pages.items.hasOwnProperty('bar'); - pages.items.hasOwnProperty('baz'); - }); - - it('should create a list from an existing list:', function () { - var pages = app.list({List: List}); - pages.addItem('foo'); - pages.addItem('bar'); - pages.addItem('baz'); - - var posts = app.list(pages); - posts.items.hasOwnProperty('foo'); - posts.items.hasOwnProperty('bar'); - posts.items.hasOwnProperty('baz'); - }); - }); - - describe('rendering items', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - - app.cache.data = {}; - }); - - it('should render a item with inherited app.render', function (done) { - app.page('test/fixtures/templates/a.tmpl') - .use(function (item) { - if (!item.contents) { - item.contents = fs.readFileSync(item.path); - } - }) - .set('data.name', 'Brian') - .render(function (err, res) { - if (err) return done(err); - assert(res.content === 'Brian'); - done(); - }); - }); - }); -}); diff --git a/test/app.lookupGenerator.js b/test/app.lookupGenerator.js new file mode 100644 index 0000000..7b4d159 --- /dev/null +++ b/test/app.lookupGenerator.js @@ -0,0 +1,61 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.lookupGenerator', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should get a generator using a custom lookup function', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + var gen = base.lookupGenerator('foo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should get a generator using a custom lookup function passed on options', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + + var gen = base.getGenerator('foo', { + lookup: function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should return undefined when nothing is found', function() { + var gen = base.lookupGenerator('fofofofofo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(!gen); + }); + + it('should throw an error when a function is not passed', function(cb) { + try { + base.lookupGenerator('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected `fn` to be a lookup function'); + cb(); + } + }); +}); diff --git a/test/app.lookups.js b/test/app.lookups.js deleted file mode 100644 index 27ecfac..0000000 --- a/test/app.lookups.js +++ /dev/null @@ -1,112 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var resolve = require('resolve-glob'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('lookups', function () { - beforeEach(function () { - app = new App(); - app.option('renameKey', function (key) { - return path.basename(key); - }); - app.create('pages') - .use(function (pages) { - pages.on('addViews', function (glob) { - var files = resolve.sync(glob); - files.forEach(function (fp) { - pages.addView(fp, {path: fp}); - }); - pages.loaded = true; - }); - return function (view) { - view.read = function () { - this.contents = fs.readFileSync(this.path); - }; - return view; - }; - }); - - app.pages('test/fixtures/templates/*.tmpl'); - }); - - describe('getView', function () { - it('should find a view', function () { - var view = app.getView('pages', 'a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should find a view using the renameKey function', function () { - var view = app.getView('pages', 'test/fixtures/templates/a.tmpl'); - assert(typeof view.path === 'string'); - }); - - it('should return null when nothing is found', function () { - var view = app.getView('pages', 'test/fixtures/templates/foo.tmpl'); - assert(view === null); - }); - - it('should find a view using a glob pattern', function () { - var view = app.getView('pages', 'a', function (key) { - return key + '.tmpl'; - }); - assert(typeof view.path === 'string'); - }); - }); - - describe('getViews', function () { - it('should return the collection object if passed:', function () { - var views = app.getViews(app.views.pages); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the plural name:', function () { - var views = app.getViews('pages'); - assert(Object.keys(views).length > 1); - }); - - it('should return the specified collection with the singular name:', function () { - var views = app.getViews('page'); - assert(Object.keys(views).length > 1); - }); - - it('should return null when the collection is not found:', function () { - (function () { - app.getViews('nada'); - }).should.throw('getViews cannot find collection: nada'); - }); - }); - - describe('find', function () { - it('should return null when a view is not found:', function () { - (function () { - app.find({}); - }).should.throw('expected name to be a string.'); - }); - - it('should find a view by collection name:', function () { - var view = app.find('a.tmpl', 'pages'); - assert(typeof view.path === 'string'); - }); - - it('should find a view by collection name:', function () { - app = new App(); - app.option('renameKey', function (key) { - return path.basename(key); - }); - app.create('pages'); - app.page('a/b/c.md', {content: '...'}); - var view = app.find('a/b/c.md'); - assert(typeof view.path === 'string'); - }); - - it('should find a view without a collection name:', function () { - var view = app.find('a.tmpl'); - assert(typeof view.path === 'string'); - }); - }); -}); diff --git a/test/app.matchGenerator.js b/test/app.matchGenerator.js new file mode 100644 index 0000000..922d5de --- /dev/null +++ b/test/app.matchGenerator.js @@ -0,0 +1,57 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var spawn = require('cross-spawn'); +var exists = require('fs-exists-sync'); +var Base = require('..'); +var base; + +describe('.matchGenerator', function() { + before(function(cb) { + if (!exists(path.resolve(__dirname, '../node_modules/generate-foo'))) { + spawn('npm', ['install', '--global', 'generate-foo'], {stdio: 'inherit'}) + .on('error', cb) + .on('close', function(code, err) { + cb(err, code); + }); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should match a generator by name', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('generate-foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by alias', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by path', function() { + base.register('generate-foo'); + var gen = base.matchGenerator(require.resolve('generate-foo')); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); +}); diff --git a/test/app.middleware.js b/test/app.middleware.js deleted file mode 100644 index 25843ed..0000000 --- a/test/app.middleware.js +++ /dev/null @@ -1,60 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('middleware', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - }); - - it('should call the all method for every middleware method:', function () { - var i = 0; - app.all(/./, function (view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should call the onLoad method when a view is loaded:', function () { - var i = 0; - app.onLoad(/./, function (view, next) { - assert(typeof view.path === 'string'); - i++; - next(); - }); - - assert(i === 0); - app.page('foo.tmpl', {content: 'foo'}); - assert(i === 1); - }); - - it('should emit an event when a handler is called:', function (done) { - var i = 0; - app.on('onLoad', function () { - i++; - }); - app.on('preRender', function () { - i++; - }); - app.on('preCompile', function () { - i++; - }); - - app.page('foo.tmpl', {content: 'foo'}) - .render(function (err) { - if (err) return done(err); - assert(i === 3); - done(); - }); - }); -}); diff --git a/test/app.option.js b/test/app.option.js deleted file mode 100644 index 12bdd6f..0000000 --- a/test/app.option.js +++ /dev/null @@ -1,98 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.option', function () { - beforeEach(function () { - app = new App(); - }); - - it('should set a key-value pair on options:', function () { - app.option('a', 'b'); - assert(app.options.a === 'b'); - }); - - it('should set an object on options:', function () { - app.option({c: 'd'}); - assert(app.options.c === 'd'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); - - it('should extend the `options` object when the first param is a string.', function() { - app.option('foo', {x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('bar', {a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('foo').should.have.property('x'); - app.option('bar').should.have.property('a'); - - app.options.foo.should.have.property('x'); - app.options.bar.should.have.property('a'); - }); - - it('should set an option.', function() { - app.option('a', 'b'); - app.options.should.have.property('a'); - }); - - it('should get an option.', function() { - app.option('a', 'b'); - app.option('a').should.equal('b'); - }); - - it('should extend the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.option('x').should.equal('xxx'); - app.option('y').should.equal('yyy'); - app.option('z').should.equal('zzz'); - }); - - it('options should be on the `options` object.', function() { - app.option({x: 'xxx', y: 'yyy', z: 'zzz'}); - app.options.x.should.equal('xxx'); - app.options.y.should.equal('yyy'); - app.options.z.should.equal('zzz'); - }); - - it('should be chainable.', function() { - app - .option({x: 'xxx', y: 'yyy', z: 'zzz'}) - .option({a: 'aaa', b: 'bbb', c: 'ccc'}); - - app.option('x').should.equal('xxx'); - app.option('a').should.equal('aaa'); - }); -}); diff --git a/test/app.page.js b/test/app.page.js new file mode 100644 index 0000000..0f314f1 --- /dev/null +++ b/test/app.page.js @@ -0,0 +1,31 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.page()', function() { + beforeEach(function() { + app = assemble(); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add page', function() { + it('should add pages to `app.views.pages`:', function() { + app.page('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.page('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.page('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.pages).length === 3); + }); + }); + + describe('load', function() { + it('should load a page from a non-glob filepath', function() { + app.page('test/fixtures/pages/a.hbs'); + assert(Object.keys(app.views.pages).length === 1); + }); + }); +}); diff --git a/test/app.pages.js b/test/app.pages.js new file mode 100644 index 0000000..1f773a2 --- /dev/null +++ b/test/app.pages.js @@ -0,0 +1,35 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var loader = require('assemble-loader'); +var update = require('..'); +var app; + +describe('.pages()', function() { + beforeEach(function() { + app = update({cli: true}); + app.use(loader()); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add pages', function() { + it('should add pages to `app.views.pages`:', function() { + app.pages({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); + + describe('load pages', function() { + it('should load pages onto `app.views.pages`:', function() { + app.pages('test/fixtures/pages/*.hbs'); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); +}); diff --git a/test/app.partial.js b/test/app.partial.js new file mode 100644 index 0000000..ba6ca57 --- /dev/null +++ b/test/app.partial.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partial()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partial', function() { + it('should add partials to `app.views.partials`:', function() { + app.partial('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.partial('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.partial('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.partials.js b/test/app.partials.js new file mode 100644 index 0000000..9622fcf --- /dev/null +++ b/test/app.partials.js @@ -0,0 +1,27 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partials()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partials', function() { + it('should add partials to `app.views.partials`:', function() { + app.partials({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')}, + }); + + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.questions.js b/test/app.questions.js new file mode 100644 index 0000000..1c36472 --- /dev/null +++ b/test/app.questions.js @@ -0,0 +1,293 @@ +'use strict'; + +process.env.NODE_ENV = 'test'; + +require('mocha'); +var fs = require('fs'); +var assert = require('assert'); +var questions = require('base-questions'); +var config = require('base-config-process'); +var store = require('base-store'); +var App = require('..'); +var app, base, site; + +describe('app.questions', function() { + describe('plugin', function() { + beforeEach(function() { + base = new App(); + base.use(config()); + base.use(questions()); + base.use(store('base-questions-tests/base')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/app')); + }); + + it('should expose a `questions` object on "app"', function() { + assert.equal(typeof app.questions, 'object'); + }); + + it('should expose a `set` method on "app.questions"', function() { + assert.equal(typeof app.questions.set, 'function'); + }); + it('should expose a `get` method on "app.questions"', function() { + assert.equal(typeof app.questions.get, 'function'); + }); + it('should expose an `ask` method on "app.questions"', function() { + assert.equal(typeof app.questions.ask, 'function'); + }); + + it('should expose an `ask` method on "app"', function() { + assert.equal(typeof app.ask, 'function'); + }); + it('should expose a `question` method on "app"', function() { + assert.equal(typeof app.question, 'function'); + }); + }); + + if (process.env.TRAVIS) { + return; + + describe('app.ask', function() { + beforeEach(function() { + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + afterEach(function() { + app.store.del({force: true}); + app.questions.clear(); + app.cache.data = {}; + }); + + it.skip('should force all questions to be asked', function(cb) { + app.questions.option('init', 'author'); + app.ask({force: true}, function(err, answers) { + console.log(answers) + cb(); + }); + }); + + it('should store a question:', function() { + app.question('a', 'b'); + assert(app.questions); + assert(app.questions.cache); + assert(app.questions.cache.a); + assert.equal(app.questions.cache.a.name, 'a'); + assert.equal(app.questions.cache.a.message, 'b'); + }); + + it.skip('should re-init a specific question:', function(cb) { + this.timeout(20000); + app.question('a', 'b'); + app.question('c', 'd'); + app.question('e', 'f'); + app.data({a: 'b'}); + + app.questions.get('e') + .force() + + app.ask(function(err, answers) { + console.log(answers); + cb(); + }); + }); + + it('should ask a question defined on `ask`', function(cb) { + app.data('name', 'Brian Woodward'); + + app.ask('name', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.name, 'Brian Woodward'); + cb(); + }); + }); + + it('should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.ask('a', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.a, 'b'); + + app.data('a', 'zzz'); + app.ask('a', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.a, 'zzz'); + cb(); + }) + }); + }); + + it('should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('a', 'this is another question'); + app.store.set('a', 'c'); + app.ask('a', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.a, 'c'); + cb(); + }) + }); + + it('should ask a question and use a config value to answer:', function(cb) { + app.question('a', 'b'); + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + + it('should prefer `cache.data` to `store.data`', function(cb) { + app.question('a', 'b'); + app.data('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'b'); + cb(); + }) + }); + + it('should update data with data loaded by config', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + }); + + describe('session data', function() { + before(function() { + site = new App(); + site.use(config()); + site.use(questions()); + site.use(store('base-questions-tests/site')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + after(function() { + site.store.del({force: true}); + site.questions.clear(); + + app.store.del({force: true}); + app.questions.clear(); + }); + + it('[app] should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('package.name', 'this is a question'); + app.data('package.name', 'base-questions'); + + app.ask('package.name', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.package.name, 'base-questions'); + + app.data('package.name', 'foo-bar-baz'); + app.ask('package.name', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }) + }); + }); + + it('[site] should ask a question and use a `cache.data` value to answer:', function(cb) { + site.question('package.name', 'this is a question'); + site.data('package.name', 'base-questions'); + + site.ask('package.name', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.package.name, 'base-questions'); + + site.data('package.name', 'foo-bar-baz'); + site.ask('package.name', function(err, answers) { + if (err) return cb(err) + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }) + }); + }); + + it('[app] should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('author.name', 'this is another question'); + app.store.set('author.name', 'Brian Woodward'); + app.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }) + }); + + it('[site] should ask a question and use a `store.data` value to answer:', function(cb) { + site.question('author.name', 'this is another question'); + site.store.set('author.name', 'Jon Schlinkert'); + site.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }) + }); + + it('[app] should ask a question and use a config value to answer:', function(cb) { + app.question('foo', 'Username?'); + app.config.process({data: {foo: 'jonschlinkert'}}, function(err) { + if (err) return cb(err); + + app.store.set('foo', 'doowb'); + + app.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'jonschlinkert'); + cb(); + }); + }); + }); + + it('[site] should ask a question and use a config value to answer:', function(cb) { + site.question('foo', 'Username?'); + site.config.process({data: {foo: 'doowb'}}, function(err) { + if (err) return cb(err); + + site.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'doowb'); + cb(); + }); + }); + }); + }); + } +}); diff --git a/test/app.register.js b/test/app.register.js new file mode 100644 index 0000000..db12253 --- /dev/null +++ b/test/app.register.js @@ -0,0 +1,234 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var generators = require('base-generators'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.register', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('function', function() { + it('should register an updater a function', function() { + base.register('foo', function() {}); + var foo = base.getGenerator('foo'); + assert(foo); + assert.equal(foo.env.alias, 'foo'); + }); + + it('should get a task from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.task('default', function() {}); + }); + var updater = base.getGenerator('foo'); + assert(updater); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('default')); + }); + + it('should get an updater from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) {}); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert.equal(bar.env.alias, 'bar'); + }); + + it('should get a sub-updater from an updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.task('something', function() {}); + }); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert(bar.tasks); + assert(bar.tasks.hasOwnProperty('something')); + }); + + it('should get a deeply-nested sub-updater registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base.getGenerator('foo.bar.baz.qux'); + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should expose the instance from each updater', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base + .getGenerator('foo') + .getGenerator('bar') + .getGenerator('baz') + .getGenerator('qux'); + + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should fail when an updater that does not exist is defined', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + }); + }); + }); + }); + var fez = base.getGenerator('foo.bar.fez'); + assert.equal(typeof fez, 'undefined'); + }); + + it('should expose the `base` instance as the second param', function(cb) { + base.register('foo', function(foo, base) { + assert(base.updaters.hasOwnProperty('foo')); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose sibling updaters on the `base` instance', function(cb) { + base.register('foo', function(foo, base) { + foo.task('abc', function() {}); + }); + base.register('bar', function(bar, base) { + assert(base.updaters.hasOwnProperty('foo')); + assert(base.updaters.hasOwnProperty('bar')); + cb(); + }); + + base.getGenerator('foo'); + base.getGenerator('bar'); + }); + }); + + describe('alias', function() { + it('should use a custom function to create the alias', function() { + base.option('toAlias', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + + base.register('base-abc-xyz', function() {}); + var xyz = base.getGenerator('xyz'); + assert(xyz); + assert.equal(xyz.env.alias, 'xyz'); + }); + }); + + describe('path', function() { + it('should register an updater function by name', function() { + base.register('foo', function() {}); + assert(base.updaters.hasOwnProperty('foo')); + }); + + it('should register an updater function by alias', function() { + base.register('abc', function() {}); + assert(base.updaters.hasOwnProperty('abc')); + }); + + it('should register an updater by dirname', function() { + base.register('a', fixtures('updaters/a')); + assert(base.updaters.hasOwnProperty('a')); + }); + + it('should register an updater from a configfile filepath', function() { + base.register('base-abc', fixtures('updaters/a/updatefile.js')); + assert(base.updaters.hasOwnProperty('base-abc')); + }); + + it('should throw when an updater does not expose the instance', function(cb) { + try { + base.register('not-exposed', require(fixtures('not-exposed.js'))); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, `cannot resolve: 'not-exposed'`); + cb(); + } + }); + }); + + describe('instance', function() { + it('should register an instance', function() { + base.register('base-inst', new Base()); + assert(base.updaters.hasOwnProperty('base-inst')); + }); + + it('should get an updater that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + assert(base.getGenerator('foo')); + }); + + it('should register multiple instances', function() { + var foo = new Base(); + var bar = new Base(); + var baz = new Base(); + base.register('foo', foo); + base.register('bar', bar); + base.register('baz', baz); + assert(base.getGenerator('foo')); + assert(base.getGenerator('bar')); + assert(base.getGenerator('baz')); + }); + + it('should get tasks from an updater that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + var updater = base.getGenerator('foo'); + assert(updater); + assert(updater.tasks.hasOwnProperty('default')); + }); + + it('should get sub-updaters from an updater registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + foo.register('bar', function() {}); + base.register('foo', foo); + var updater = base.getGenerator('foo.bar'); + assert(updater); + }); + + it('should get tasks from sub-updaters registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + + foo.options.isFoo = true; + foo.register('bar', function(bar) { + bar.task('whatever', function() {}); + }); + + base.register('foo', foo); + var updater = base.getGenerator('foo.bar'); + assert(updater.tasks); + assert(updater.tasks.hasOwnProperty('whatever')); + }); + }); +}); diff --git a/test/app.render.js b/test/app.render.js deleted file mode 100644 index e796d95..0000000 --- a/test/app.render.js +++ /dev/null @@ -1,88 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should throw an error when no callback is given:', function () { - (function() { - app.render({}); - }).should.throw('Templates#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function (done) { - app.page('foo.bar', {content: '<%= name %>'}); - var page = app.pages.getView('foo.bar'); - - app.render(page, function(err) { - assert(err.message === 'Templates#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function (done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function (done) { - var locals = {name: 'Halle'}; - app.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - app.page('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function (done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function (done) { - app.pages('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = app.pages.getView('a.tmpl'); - - app.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - }); -}); diff --git a/test/app.renderFile.js b/test/app.renderFile.js deleted file mode 100644 index b49fd7f..0000000 --- a/test/app.renderFile.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -var update = require('..'); -var assert = require('assert'); -var should = require('should'); -var path = require('path'); -var app; - -describe('app.renderFile()', function() { - beforeEach(function () { - app = update(); - app.engine('hbs', require('engine-handlebars')); - app.engine('*', require('engine-base')); - - app.create('files', {engine: '*'}); - app.file('a', {content: 'this is <%= title() %>'}); - app.file('b', {content: 'this is <%= title() %>'}); - app.file('c', {content: 'this is <%= title() %>'}); - - app.option('renameKey', function (key) { - return path.basename(key, path.extname(key)); - }); - - app.helper('title', function () { - var view = this.context.view; - var key = view.key; - var ctx = this.context[key]; - if (ctx && ctx.title) return ctx.title; - return key; - }); - }); - - it('should render views from src', function (done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function (file) { - files.push(file); - }) - .on('end', function () { - assert.equal(files[0].basename, 'a.hbs'); - assert.equal(files[1].basename, 'b.hbs'); - assert.equal(files[2].basename, 'c.hbs'); - done(); - }); - }); - - it('should render views with the engine that matches the file extension', function (done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile()) - .on('error', done) - .on('data', function (file) { - files.push(file); - }) - .on('end', function () { - assert(/

a<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render views from src with the engine passed on the opts', function (done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({engine: '*'})) - .on('error', done) - .on('data', function (file) { - files.push(file); - }) - .on('end', function () { - assert(/

a<\/h2>/.test(files[0].content)); - assert(/

b<\/h2>/.test(files[1].content)); - assert(/

c<\/h2>/.test(files[2].content)); - done(); - }); - }); - - it('should use the context passed on the opts', function (done) { - var stream = app.src(path.join(__dirname, 'fixtures/pages/*.hbs')); - var files = []; - - stream.pipe(app.renderFile({a: {title: 'foo'}})) - .on('error', done) - .on('data', function (file) { - files.push(file); - }) - .on('end', function () { - assert(/

foo<\/h1>/.test(files[0].content)); - assert(/

b<\/h1>/.test(files[1].content)); - assert(/

c<\/h1>/.test(files[2].content)); - done(); - }); - }); - - it('should render the files in a collection', function (cb) { - var files = []; - app.toStream('files') - .pipe(app.renderFile()) - .on('error', cb) - .on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file); - }) - .on('end', function () { - assert(/this is a/.test(files[0].content)); - assert(/this is b/.test(files[1].content)); - assert(/this is c/.test(files[2].content)); - assert.equal(files.length, 3); - cb(); - }); - }); - - it('should handle engine errors', function (cb) { - app.create('notdefined', {engine: '*'}); - app.notdefined('foo', {content: '<%= bar %>'}); - app.toStream('notdefined') - .pipe(app.renderFile()) - .on('error', function (err) { - assert.equal(typeof err, 'object'); - assert.equal(err.message, 'bar is not defined'); - cb(); - }) - .on('end', function () { - cb(new Error('expected renderFile to handle the error.')); - }); - }); -}); diff --git a/test/app.route.js b/test/app.route.js deleted file mode 100644 index c7ab262..0000000 --- a/test/app.route.js +++ /dev/null @@ -1,93 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('routes', function () { - beforeEach(function() { - app = new App(); - }); - - describe('routes', function() { - it('should create a route for the given path:', function (done) { - app = new App(); - app.create('posts'); - - app.on('all', function(msg) { - assert(msg === 'done'); - done(); - }); - - app.route('blog/:title') - .all(function(view, next) { - app.emit('all', 'done'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit events when a route method is called:', function (done) { - app = new App(); - app.create('posts'); - - app.on('onLoad', function(view) { - assert(view.path === 'blog/foo.js'); - done(); - }); - - app.param('title', function (view, next, title) { - assert(title === 'foo.js'); - next(); - }); - - app.onLoad('blog/:title', function (view, next) { - assert(view.path === 'blog/foo.js'); - next(); - }); - - app.post('whatever', {path: 'blog/foo.js', content: 'bar baz'}); - }); - - it('should emit errors', function (done) { - app = new App(); - app.create('posts'); - - app.on('error', function(err) { - assert(err.message === 'false == true'); - done(); - }); - - // wrong... - app.param('title', function (view, next, title) { - assert(title === 'fo.js'); - next(); - }); - - app.onLoad('/blog/:title', function (view, next) { - assert(view.path === '/blog/foo.js'); - next(); - }); - - app.post('whatever', {path: '/blog/foo.js', content: 'bar baz'}); - }); - - it('should have path property', function () { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function () {} - ]); - route.path.should.equal('/blog/:year/:month/:day/:slug'); - }); - - it('should have stack property', function () { - var route = new app.Route('/blog/:year/:month/:day/:slug').all([ - function () {} - ]); - - route.stack.should.be.instanceof(Array); - route.stack.should.have.length(1); - }); - }); -}); diff --git a/test/app.src.js b/test/app.src.js deleted file mode 100644 index bd2b089..0000000 --- a/test/app.src.js +++ /dev/null @@ -1,295 +0,0 @@ -'use strict'; - -var App = require('..'); -var assert = require('assert'); -var should = require('should'); -var join = require('path').join; -var app; - -describe('src()', function() { - beforeEach(function () { - app = new App(); - }); - - it('should return a stream', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - assert(stream); - assert.equal(typeof stream.on, 'function'); - assert.equal(typeof stream.pipe, 'function'); - done(); - }); - - it('should return an input stream from a flat glob', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function () { - done(); - }); - }); - - it('should return an input stream for multiple globs', function (done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function () { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs with negation', function (done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function () { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function () { - done(); - }); - }); - - it('should return an input stream with contents as stream when buffer is false', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - var buf = ''; - file.contents.on('data', function (d) { - buf += d; - }); - file.contents.on('end', function () { - buf.should.equal('Hello world!'); - done(); - }); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - }); - - it('should return an input stream from a deep glob', function (done) { - var stream = app.src(join(__dirname, './fixtures/**/*.jade')); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }); - stream.on('end', function () { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function (done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function () { - ++a; - }); - stream.on('end', function () { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function (done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function (file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function () { - a.should.equal(1); - done(); - }); - }); - - it('should return a stream', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - should.exist(stream); - should.exist(stream.on); - done(); - }); - - it('should return an input stream from a flat glob', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee')); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function () { - done(); - }); - }); - - it('should return an input stream for multiple globs', function (done) { - var globArray = [ - join(__dirname, './fixtures/generic/run.dmc'), - join(__dirname, './fixtures/generic/test.dmc') - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function () { - files.length.should.equal(2); - files[0].path.should.equal(globArray[0]); - files[1].path.should.equal(globArray[1]); - done(); - }); - }); - - it('should return an input stream for multiple globs, with negation', function (done) { - var expectedPath = join(__dirname, './fixtures/generic/run.dmc'); - var globArray = [ - join(__dirname, './fixtures/generic/*.dmc'), - '!' + join(__dirname, './fixtures/generic/test.dmc'), - ]; - var stream = app.src(globArray); - - var files = []; - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - files.push(file); - }); - stream.on('end', function () { - files.length.should.equal(1); - files[0].path.should.equal(expectedPath); - done(); - }); - }); - - it('should return an input stream with no contents when read is false', function (done) { - var stream = app.src(join(__dirname, './fixtures/*.coffee'), {read: false}); - stream.on('error', done); - stream.on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.not.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - }); - stream.on('end', function () { - done(); - }); - }); - - it.skip('should throw an error when buffer is false', function (done) { - app.src(join(__dirname, './fixtures/*.coffee'), {buffer: false}) - .on('error', function () { - done(); - }) - .on('data', function () { - done(new Error('should have thrown an error')); - }); - }); - - it('should return an input stream from a deep glob', function (done) { - app.src(join(__dirname, './fixtures/**/*.jade')) - .on('error', done) - .on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); - String(file.contents).should.equal('test template'); - }) - .on('end', function () { - done(); - }); - }); - - it('should return an input stream from a deeper glob', function (done) { - var stream = app.src(join(__dirname, './fixtures/**/*.dmc')); - var a = 0; - stream.on('error', done); - stream.on('data', function () { - ++a; - }); - stream.on('end', function () { - a.should.equal(2); - done(); - }); - }); - - it('should return a file stream from a flat path', function (done) { - var a = 0; - var stream = app.src(join(__dirname, './fixtures/test.coffee')); - stream.on('error', done); - stream.on('data', function (file) { - ++a; - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); - String(file.contents).should.equal('Hello world!'); - }); - stream.on('end', function () { - a.should.equal(1); - done(); - }); - }); -}); diff --git a/test/app.store.js b/test/app.store.js new file mode 100644 index 0000000..7c44e4e --- /dev/null +++ b/test/app.store.js @@ -0,0 +1,335 @@ +'use strict'; + +require('mocha'); +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); +var store = require('base-store'); +var App = require('..'); +var app; + +describe('store', function() { + describe('methods', function() { + beforeEach(function() { + app = new App(); + app.use(store()); + app.store.create('app-data-tests'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should `.set()` a value on the store', function() { + app.store.set('one', 'two'); + assert.equal(app.store.data.one, 'two'); + }); + + it('should `.set()` an object', function() { + app.store.set({four: 'five', six: 'seven'}); + assert.equal(app.store.data.four, 'five'); + assert.equal(app.store.data.six, 'seven'); + }); + + it('should `.set()` a nested value', function() { + app.store.set('a.b.c.d', {e: 'f'}); + assert.equal(app.store.data.a.b.c.d.e, 'f'); + }); + + it('should `.union()` a value on the store', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should not union duplicate values', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + + app.store.union('one', ['two']); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should concat an existing array:', function() { + app.store.union('one', 'a'); + assert.deepEqual(app.store.data.one, ['a']); + + app.store.union('one', ['b']); + assert.deepEqual(app.store.data.one, ['a', 'b']); + + app.store.union('one', ['c', 'd']); + assert.deepEqual(app.store.data.one, ['a', 'b', 'c', 'd']); + }); + + it('should return true if a key `.has()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.has('foo')); + assert(app.store.has('baz')); + assert(!app.store.has('bar')); + assert(!app.store.has('qux')); + }); + + it('should return true if a nested key `.has()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.has('a.b.bar')); + assert(app.store.has('a.b.c.d')); + assert(app.store.has('a.b.c.d.x')); + assert(!app.store.has('a.b.c.d.z')); + assert(app.store.has('a.b.c.e')); + assert(app.store.has('a.b.c.e.f')); + assert(!app.store.has('a.b.c.e.z')); + assert(app.store.has('a.b.g.j')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.has('a.b.g.j.z')); + }); + + it('should return true if a key exists `.hasOwn()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.hasOwn('foo')); + assert(!app.store.hasOwn('bar')); + assert(app.store.hasOwn('baz')); + assert(app.store.hasOwn('qux')); + }); + + it('should return true if a nested key exists `.hasOwn()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.hasOwn('a.b.bar')); + assert(app.store.hasOwn('a.b.c.d')); + assert(app.store.hasOwn('a.b.c.d.x')); + assert(!app.store.hasOwn('a.b.c.d.z')); + assert(app.store.has('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.c.e.f')); + assert(!app.store.hasOwn('a.b.c.e.bar')); + assert(!app.store.has('a.b.g.j.k')); + assert(app.store.hasOwn('a.b.g.j.k')); + assert(!app.store.hasOwn('a.b.g.j.foo')); + }); + + it('should `.get()` a stored value', function() { + app.store.set('three', 'four'); + assert.equal(app.store.get('three'), 'four'); + }); + + it('should `.get()` a nested value', function() { + app.store.set({a: {b: {c: 'd'}}}); + assert.equal(app.store.get('a.b.c'), 'd'); + }); + + it('should `.del()` a stored value', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + assert(app.store.data.hasOwnProperty('a')); + assert(app.store.data.hasOwnProperty('c')); + + app.store.del('a'); + app.store.del('c'); + assert(!app.store.data.hasOwnProperty('a')); + assert(!app.store.data.hasOwnProperty('c')); + }); + + it('should `.del()` multiple stored values', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.del(['a', 'c', 'e']); + assert.deepEqual(app.store.data, {}); + }); + }); +}); + +describe('create', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + + // init the actual store json file + app.store.set('a', 'b'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should expose a `create` method', function() { + assert.equal(typeof app.store.create, 'function'); + }); + + it('should create a "sub-store" with the given name', function() { + var store = app.store.create('created'); + assert.equal(store.name, 'created'); + }); + + it('should create a "sub-store" with the project name when no name is passed', function() { + var store = app.store.create('app-store'); + assert.equal(store.name, 'app-store'); + }); + + it('should throw an error when a conflicting store name is used', function(cb) { + try { + app.store.create('create'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'Cannot create store: "create", since "create" is a reserved property key. Please choose a different store name.'); + cb(); + } + }); + + it('should add a store object to store[name]', function() { + app.store.create('foo'); + assert.equal(typeof app.store.foo, 'object'); + assert.equal(typeof app.store.foo.set, 'function'); + app.store.foo.del({force: true}); + }); + + it('should save the store in a namespaced directory under the parent', function() { + app.store.create('foo'); + var dir = path.dirname(app.store.path); + + assert.equal(app.store.foo.path, path.join(dir, 'update/foo.json')); + app.store.foo.set('a', 'b'); + app.store.foo.del({force: true}); + }); + + it('should set values on the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.data.a, 'b'); + app.store.foo.del({force: true}); + }); + + it('should get values from the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.get('a'), 'b'); + app.store.foo.del({force: true}); + }); +}); + +describe('events', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should emit `set` when an object is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set({a: {b: {c: 'd'}}}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when a key/value pair is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', 'b'); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an object value is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an array of objects is passed:', function(cb) { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set([{a: 'b'}, {c: 'd'}]); + assert.deepEqual(keys, ['a', 'c']); + cb(); + }); + + it('should emit `has`:', function(cb) { + var keys = []; + + app.store.on('has', function(val) { + assert(val); + cb(); + }); + + app.store.set('a', 'b'); + app.store.has('a'); + }); + + it('should emit `del` when a value is delted:', function(cb) { + app.store.on('del', function(keys) { + assert.deepEqual(keys, 'a'); + assert.equal(typeof app.store.get('a'), 'undefined'); + cb(); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(app.store.get('a'), {b: 'c'}); + app.store.del('a'); + }); + + it('should emit deleted keys on `del`:', function(cb) { + var arr = []; + + app.store.on('del', function(key) { + arr.push(key); + assert.equal(Object.keys(app.store.data).length, 0); + }); + + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + app.store.del({force: true}); + assert.deepEqual(arr, ['a', 'c', 'e']); + cb(); + }); + + it('should throw an error if force is not passed', function(cb) { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + try { + app.store.del(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'options.force is required to delete the entire cache.'); + cb(); + } + }); +}); diff --git a/test/app.symlink.js b/test/app.symlink.js deleted file mode 100644 index 8a63fea..0000000 --- a/test/app.symlink.js +++ /dev/null @@ -1,396 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('graceful-fs'); -var path = require('path'); -var rimraf = require('rimraf'); -var bufEqual = require('buffer-equal'); -var through = require('through2'); -var File = require('vinyl'); -var update = require('..'); -var spies = require('./support/spy'); -var chmodSpy = spies.chmodSpy; -var statSpy = spies.statSpy; -var app, bufferStream; - -var wipeOut = function(cb) { - rimraf(path.join(__dirname, './actual/'), cb); - spies.setError('false'); - statSpy.reset(); - chmodSpy.reset(); - app = update(); -}; - -var dataWrap = function(fn) { - return function(data, enc, cb) { - fn(data); - cb(); - }; -}; - -var realMode = function(n) { - return n & 07777; -}; - -describe('symlink stream', function() { - beforeEach(wipeOut); - afterEach(wipeOut); - - it('should pass through writes with cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should pass through writes with default cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - - var expectedFile = new File({ - base: __dirname, - cwd: __dirname, - path: inputPath, - contents: null - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - done(); - }; - - var stream = app.symlink(path.join(__dirname, './actual/')); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should make link to the right folder with relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder with function and relative cwd', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedBase = path.join(__dirname, './actual'); - var expectedContents = fs.readFileSync(inputPath); - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink(function(file){ - should.exist(file); - file.should.equal(expectedFile); - return './actual'; - }, {cwd: path.relative(process.cwd(), __dirname)}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write buffer files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should write streaming files to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/test.coffee'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var contentStream = through.obj(); - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: contentStream, - stat: { - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.existsSync(expectedPath).should.equal(true); - bufEqual(fs.readFileSync(expectedPath), expectedContents).should.equal(true); - fs.readlinkSync(expectedPath).should.equal(inputPath); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - setTimeout(function(){ - contentStream.write(expectedContents); - contentStream.end(); - }, 100); - stream.end(); - }); - - it('should write directories to the right folder', function(done) { - var inputPath = path.join(__dirname, './fixtures/wow'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedPath = path.join(__dirname, './actual/wow'); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0655; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: null, - stat: { - isDirectory: function(){ - return true; - }, - mode: expectedMode - } - }); - - var onEnd = function(){ - buffered.length.should.equal(1); - buffered[0].should.equal(expectedFile); - buffered[0].cwd.should.equal(__dirname, 'cwd should have changed'); - buffered[0].base.should.equal(expectedBase, 'base should have changed'); - buffered[0].path.should.equal(expectedPath, 'path should have changed'); - fs.readlinkSync(expectedPath).should.equal(inputPath); - fs.lstatSync(expectedPath).isDirectory().should.equal(false); - fs.statSync(expectedPath).isDirectory().should.equal(true); - done(); - }; - - var stream = app.symlink('./actual/', {cwd: __dirname}); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - stream.pipe(bufferStream); - stream.write(expectedFile); - stream.end(); - }); - - it('should use different modes for files and directories', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - var expectedBase = path.join(__dirname, './actual/wow'); - var expectedDirMode = 0755; - var expectedFileMode = 0655; - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - realMode(fs.lstatSync(expectedBase).mode).should.equal(expectedDirMode); - realMode(buffered[0].stat.mode).should.equal(expectedFileMode); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - mode: expectedFileMode, - dirMode: expectedDirMode - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should change to the specified base', function(done) { - var inputBase = path.join(__dirname, './fixtures'); - var inputPath = path.join(__dirname, './fixtures/wow/suchempty'); - - var firstFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - stat: fs.statSync(inputPath) - }); - - var onEnd = function(){ - buffered[0].base.should.equal(inputBase); - done(); - }; - - var stream = app.symlink('./actual/', { - cwd: __dirname, - base: inputBase - }); - - var buffered = []; - bufferStream = through.obj(dataWrap(buffered.push.bind(buffered)), onEnd); - - stream.pipe(bufferStream); - stream.write(firstFile); - stream.end(); - }); - - it('should report IO errors', function(done) { - var inputPath = path.join(__dirname, './fixtures/test.coffee'); - var inputBase = path.join(__dirname, './fixtures/'); - var expectedContents = fs.readFileSync(inputPath); - var expectedBase = path.join(__dirname, './actual'); - var expectedMode = 0722; - - var expectedFile = new File({ - base: inputBase, - cwd: __dirname, - path: inputPath, - contents: expectedContents, - stat: { - mode: expectedMode - } - }); - - fs.mkdirSync(expectedBase); - fs.chmodSync(expectedBase, 0); - - var stream = app.symlink('./actual/', {cwd: __dirname}); - stream.on('error', function(err) { - err.code.should.equal('EACCES'); - done(); - }); - stream.write(expectedFile); - }); - - ['end', 'finish'].forEach(function(eventName) { - it('should emit ' + eventName + ' event', function(done) { - var srcPath = path.join(__dirname, './fixtures/test.coffee'); - var stream = app.symlink('./actual/', {cwd: __dirname}); - - stream.on(eventName, function() { - done(); - }); - - var file = new File({ - path: srcPath, - cwd: __dirname, - contents: new Buffer("1234567890") - }); - - stream.write(file); - stream.end(); - }); - }); -}); diff --git a/test/app.task.js b/test/app.task.js index 3b73918..68ad212 100644 --- a/test/app.task.js +++ b/test/app.task.js @@ -1,96 +1,132 @@ 'use strict'; +require('mocha'); var assert = require('assert'); -var App = require('..'); -var app; +var Base = require('..'); +var base; -describe('task()', function () { - beforeEach(function () { - app = new App(); +describe('.generate', function() { + beforeEach(function() { + base = new Base(); }); - it('should register a task', function () { - var fn = function (done) { - done(); + it('should register a task', function() { + var fn = function(cb) { + cb(); }; - app.task('default', fn); - assert.equal(typeof app.tasks.default, 'object'); - assert.equal(app.tasks.default.fn, fn); + base.task('default', fn); + assert.equal(typeof base.tasks.default, 'object'); + assert.equal(base.tasks.default.fn, fn); }); - it('should register a task with an array of dependencies', function () { - app.task('default', ['foo', 'bar'], function (done) { - done(); + it('should register a task with an array of dependencies', function(cb) { + var count = 0; + base.task('foo', function(next) { + count++; + next(); }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); - }); - - it('should register a task with a list of strings as dependencies', function () { - app.task('default', 'foo', 'bar', function (done) { - done(); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('default', ['foo', 'bar'], function(next) { + count++; + next(); + }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); }); - assert.equal(typeof app.tasks.default, 'object'); - assert.deepEqual(app.tasks.default.deps, ['foo', 'bar']); }); - it('should run a task', function (done) { + it('should run a glob of tasks', function(cb) { var count = 0; - app.task('default', function (cb) { + base.task('foo', function(next) { + count++; + next(); + }); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('baz', function(next) { + count++; + next(); + }); + base.task('qux', function(next) { count++; + next(); + }); + base.task('default', ['b*']); + assert.equal(typeof base.tasks.default, 'object'); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 2); cb(); }); + }); - app.build('default', function (err) { - if (err) return done(err); - assert.equal(count, 1); - done(); + it('should register a task with a list of strings as dependencies', function() { + base.task('default', 'foo', 'bar', function(cb) { + cb(); }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); }); - it('should throw an error when a task with unregistered dependencies is run', function (done) { + it('should run a task', function(cb) { var count = 0; - app.task('default', ['foo', 'bar'], function (cb) { + base.task('default', function(cb) { count++; cb(); }); - app.build('default', function (err) { - if (!err) return done(new Error('Expected an error to be thrown.')); - assert.equal(count, 0); - done(); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function(cb) { + base.task('default', ['foo', 'bar']); + base.build('default', function(err) { + assert(err); + cb(); }); }); - it('should throw an error when `.build` is called without a callback function.', function () { + it('should throw an error when `.build` is called without a callback function.', function() { try { - app.build('default'); + base.build('default'); throw new Error('Expected an error to be thrown.'); } catch (err) { } }); - it('should emit task events', function (done) { + it('should emit task events', function(cb) { var events = []; - app.on('task:starting', function(task) { + base.on('task:starting', function(task) { events.push('starting.' + task.name); }); - app.on('task:finished', function(task) { + base.on('task:finished', function(task) { events.push('finished.' + task.name); }); - app.on('task:error', function(err, task) { + base.on('task:error', function(e, task) { events.push('error.' + task.name); }); - - app.task('foo', function (cb) { + base.task('foo', function(cb) { cb(); }); - app.task('bar', ['foo'], function (cb) { + base.task('bar', ['foo'], function(cb) { cb(); }); - app.task('default', ['bar']); - app.build('default', function (err) { - if (err) return done(err); + base.task('default', ['bar']); + base.build('default', function(err) { + if (err) return cb(err); assert.deepEqual(events, [ 'starting.default', 'starting.bar', @@ -99,60 +135,57 @@ describe('task()', function () { 'finished.bar', 'finished.default' ]); - done(); + cb(); }); }); - it('should emit an error event when an error is passed back in a task', function (done) { - app.on('error', function (err) { + it('should emit an error event when an error is passed back in a task', function(cb) { + base.on('error', function(err) { assert(err); assert.equal(err.message, 'This is an error'); }); - app.task('default', function (cb) { + base.task('default', function(cb) { return cb(new Error('This is an error')); }); - app.build('default', function (err) { - if (err) return done(); - done(new Error('Expected an error')); + base.build('default', function(err) { + if (err) return cb(); + cb(new Error('Expected an error')); }); }); - it('should emit an error event when an error is thrown in a task', function (done) { - var errors = 0; - app.on('error', function (err) { - errors++; + it('should emit an error event when an error is thrown in a task', function(cb) { + base.on('error', function(err) { assert(err); assert.equal(err.message, 'This is an error'); }); - app.task('default', function (cb) { + base.task('default', function(cb) { cb(new Error('This is an error')); }); - app.build('default', function (err) { - assert.equal(errors, 1); - if (err) return done(); - done(new Error('Expected an error')); + base.build('default', function(err) { + assert(err); + cb(); }); }); - it('should run dependencies before running the dependent task.', function (done) { + it('should run dependencies before running the dependent task.', function(cb) { var seq = []; - app.task('foo', function (cb) { + base.task('foo', function(cb) { seq.push('foo'); cb(); }); - app.task('bar', function (cb) { + base.task('bar', function(cb) { seq.push('bar'); cb(); }); - app.task('default', ['foo', 'bar'], function (cb) { + base.task('default', ['foo', 'bar'], function(cb) { seq.push('default'); cb(); }); - app.build('default', function (err) { - if (err) return done(err); + base.build('default', function(err) { + if (err) return cb(err); assert.deepEqual(seq, ['foo', 'bar', 'default']); - done(); + cb(); }); }); }); diff --git a/test/app.toAlias.js b/test/app.toAlias.js new file mode 100644 index 0000000..e5f8bf4 --- /dev/null +++ b/test/app.toAlias.js @@ -0,0 +1,41 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.toAlias', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should not create an alias when no prefix is given', function() { + assert.equal(base.toAlias('foo-bar'), 'foo-bar'); + }); + + it('should create an alias using the `options.toAlias` function', function() { + var alias = base.toAlias('one-two-three', { + toAlias: function(name) { + return name.slice(name.lastIndexOf('-') + 1); + } + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using the given function', function() { + var alias = base.toAlias('one-two-three', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using base.options.toAlias function', function() { + base.options.toAlias = function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }; + + var alias = base.toAlias('one-two-three'); + assert.equal(alias, 'three'); + }); +}); diff --git a/test/app.toStream.js b/test/app.toStream.js deleted file mode 100644 index 326b84c..0000000 --- a/test/app.toStream.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -var update = require('..'); -var assert = require('assert'); -var should = require('should'); -var app; - -describe('toStream()', function() { - beforeEach(function () { - app = update(); - app.create('pages'); - app.page('a', {content: 'this is A'}); - app.page('b', {content: 'this is B'}); - app.page('c', {content: 'this is C'}); - - app.create('posts'); - app.post('x', {content: 'this is X'}); - app.post('y', {content: 'this is Y'}); - app.post('z', {content: 'this is Z'}); - }); - - it('should return a stream', function (cb) { - var stream = app.toStream(); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should return a stream for a collection', function (cb) { - var stream = app.toStream('pages'); - should.exist(stream); - should.exist(stream.on); - cb(); - }); - - it('should stack handle multiple collections', function (cb) { - var files = []; - app.toStream('pages') - .pipe(app.toStream('posts')) - .on('data', function(file) { - files.push(file); - }) - .on('end', function () { - assert.equal(files.length, 6); - cb(); - }); - }); - - it('should push each item in the collection into the stream', function (cb) { - var files = []; - app.toStream('pages') - .on('error', cb) - .on('data', function (file) { - should.exist(file); - should.exist(file.path); - should.exist(file.contents); - files.push(file.path); - }) - .on('end', function () { - assert.equal(files.length, 3); - cb(); - }); - }); -}); \ No newline at end of file diff --git a/test/app.update-array.js b/test/app.update-array.js new file mode 100644 index 0000000..1964250 --- /dev/null +++ b/test/app.update-array.js @@ -0,0 +1,931 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.update.js b/test/app.update.js new file mode 100644 index 0000000..b1403d9 --- /dev/null +++ b/test/app.update.js @@ -0,0 +1,961 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should throw an error when a task is not found (task array)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl', ['foo'], function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should throw an error when a task is not found (task string)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl:foo', function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.updater.js b/test/app.updater.js new file mode 100644 index 0000000..3126482 --- /dev/null +++ b/test/app.updater.js @@ -0,0 +1,291 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); + +describe('.updater', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^updater-(.*)/, '$1'); + }); + }); + + describe('updater', function() { + it('should get an updater by alias', function() { + base.register('updater-example', require('updater-example')); + var gen = base.getUpdater('example'); + assert(gen); + assert.equal(gen.env.name, 'updater-example'); + assert.equal(gen.env.alias, 'example'); + }); + + it('should get an updater using a custom lookup function', function() { + base.register('updater-foo', function() {}); + base.register('updater-bar', function() {}); + + var gen = base.getUpdater('foo', { + lookup: function(key) { + return ['updater-' + key, 'verb-' + key + '-updater', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'updater-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + }); + + describe('register > function', function() { + it('should register an updater function by name', function() { + base.updater('foo', function() {}); + assert(base.updaters.hasOwnProperty('foo')); + }); + + it('should register an updater function by alias', function() { + base.updater('updater-abc', function() {}); + assert(base.updaters.hasOwnProperty('updater-abc')); + }); + }); + + describe('get > alias', function() { + it('should get an updater by alias', function() { + base.updater('updater-abc', function() {}); + var abc = base.updater('abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('get > name', function() { + it('should get an updater by name', function() { + base.updater('updater-abc', function() {}); + var abc = base.updater('updater-abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('updaters', function() { + it('should invoke a registered updater when `getGenerator` is called', function(cb) { + base.register('foo', function(app) { + app.task('default', function() {}); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose the updater instance on `app`', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.get('a'), 'b'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "base" instance on `base`', function(cb) { + base.set('x', 'z'); + base.register('foo', function(app, base) { + app.task('default', function(next) { + assert.equal(base.get('x'), 'z'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "env" object on `env`', function(cb) { + base.register('foo', function(app, base, env) { + app.task('default', function(next) { + assert.equal(env.alias, 'foo'); + next(); + }); + }); + + base.getGenerator('foo').build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose an app\'s updaters on app.updaters', function(cb) { + base.register('foo', function(app) { + app.register('a', function() {}); + app.register('b', function() {}); + + app.updaters.hasOwnProperty('a'); + app.updaters.hasOwnProperty('b'); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should expose all root updaters on base.updaters', function(cb) { + base.register('foo', function(app, b) { + b.updaters.hasOwnProperty('foo'); + b.updaters.hasOwnProperty('bar'); + b.updaters.hasOwnProperty('baz'); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + }); + + describe('cross-updaters', function() { + it('should get an updater from another updater', function(cb) { + base.register('foo', function(app, b) { + var bar = b.getGenerator('bar'); + assert(bar); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + + it('should set options on another updater instance', function(cb) { + base.updater('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.option('abc'), 'xyz'); + next(); + }); + }); + + base.updater('bar', function(app, b) { + var foo = b.getGenerator('foo'); + foo.option('abc', 'xyz'); + foo.build(function(err) { + if (err) return cb(err); + cb(); + }); + }); + }); + }); + + describe('updaters > filepath', function() { + it('should register an updater function from a file path', function() { + var one = base.updater('one', fixtures('one/updatefile.js')); + assert(base.updaters.hasOwnProperty('one')); + assert(typeof base.updaters.one === 'object'); + assert.deepEqual(base.updaters.one, one); + }); + + it('should get a registered updater by name', function() { + var one = base.updater('one', fixtures('one/updatefile.js')); + assert.deepEqual(base.updater('one'), one); + }); + }); + + describe('tasks', function() { + it('should expose an updater\'s tasks on app.tasks', function(cb) { + base.register('foo', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + assert(app.tasks.a); + assert(app.tasks.b); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should get tasks from another updater', function(cb) { + base.register('foo', function(app, b) { + var baz = b.getGenerator('baz'); + var task = baz.tasks.aaa; + assert(task); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) { + app.task('aaa', function() {}); + }); + base.getGenerator('foo'); + }); + }); + + describe('namespace', function() { + it('should expose `app.namespace`', function(cb) { + base.updater('foo', function(app) { + assert(typeof app.namespace, 'string'); + cb(); + }); + }); + + it('should create namespace from updater alias', function(cb) { + base.updater('updater-foo', function(app) { + assert.equal(app.namespace, base._name + '.foo'); + cb(); + }); + }); + + it('should create sub-updater namespace from parent namespace and alias', function(cb) { + var name = base._name; + base.updater('updater-foo', function(app) { + assert.equal(app.namespace, name + '.foo'); + + app.updater('updater-bar', function(bar) { + assert.equal(bar.namespace, name + '.foo.bar'); + + bar.updater('updater-baz', function(baz) { + assert.equal(baz.namespace, name + '.foo.bar.baz'); + + baz.updater('updater-qux', function(qux) { + assert.equal(qux.namespace, name + '.foo.bar.baz.qux'); + cb(); + }); + }); + }); + }); + }); + + it('should expose namespace on `this`', function(cb) { + var name = base._name; + + base.updater('updater-foo', function(app, first) { + assert.equal(this.namespace, base._name + '.foo'); + + this.updater('updater-bar', function() { + assert.equal(this.namespace, base._name + '.foo.bar'); + + this.updater('updater-baz', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz'); + + this.updater('updater-qux', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz.qux'); + assert.equal(app.namespace, base._name + '.foo'); + assert.equal(first.namespace, base._name); + cb(); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/app.use.js b/test/app.use.js deleted file mode 100644 index 875e858..0000000 --- a/test/app.use.js +++ /dev/null @@ -1,281 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var app; - -describe('app.use', function () { - beforeEach(function () { - app = new App(); - }); - - it('should expose the instance to `use`:', function (done) { - app.use(function (inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should be chainable:', function (done) { - app.use(function (inst) { - assert(inst instanceof App); - }) - .use(function (inst) { - assert(inst instanceof App); - }) - .use(function (inst) { - assert(inst instanceof App); - done(); - }); - }); - - it('should pass to collection `use` if a function is returned:', function () { - app.use(function (inst) { - assert(inst instanceof App); - return function (collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .addView({path: 'b.md', content: '...'}) - .addView({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a collection function is returned:', function () { - app - .use(function (inst) { - assert(inst instanceof App); - return function (collection) { - collection.foo = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - return function (collection) { - collection.bar = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - return function (collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - return collection; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should pass to view `use` if collection.use returns a function:', function () { - app.use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function (view) { - assert(view instanceof View); - view.foo = collection.addView.bind(collection); - return view; - }; - }; - }); - - app.create('pages') - .foo({path: 'a.md', content: '...'}) - .foo({path: 'b.md', content: '...'}) - .foo({path: 'c.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - }); - - it('should be chainable when a view function is returned:', function () { - app - .use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function (view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function (view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.baz = collection.addView; - - return function (view) { - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - }); - - it('should work with multiple collections:', function () { - app - .use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.foo = collection.addView; - - return function (view) { - assert(view instanceof View); - view.a = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - - return function (collection) { - assert(collection instanceof Views); - collection.bar = collection.addView; - - return function (view) { - assert(view instanceof View); - view.b = collection.addView.bind(collection); - return view; - }; - }; - }) - .use(function (inst) { - assert(inst instanceof App); - assert(this instanceof App); - - return function (collection) { - collection.baz = collection.addView; - assert(collection instanceof Views); - assert(this instanceof Views); - - return function (view) { - assert(this instanceof View); - assert(view instanceof View); - view.c = collection.addView.bind(collection); - return view; - }; - }; - }); - - var pages = app.create('pages'); - - pages.foo({path: 'a.md', content: '...'}); - pages.bar({path: 'b.md', content: '...'}); - pages.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.pages.hasOwnProperty('a.md')); - assert(app.views.pages.hasOwnProperty('b.md')); - assert(app.views.pages.hasOwnProperty('c.md')); - - assert(app.views.pages.hasOwnProperty('x.md')); - assert(app.views.pages.hasOwnProperty('y.md')); - assert(app.views.pages.hasOwnProperty('z.md')); - - var posts = app.create('posts'); - - posts.foo({path: 'a.md', content: '...'}); - posts.bar({path: 'b.md', content: '...'}); - posts.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.posts.hasOwnProperty('a.md')); - assert(app.views.posts.hasOwnProperty('b.md')); - assert(app.views.posts.hasOwnProperty('c.md')); - - assert(app.views.posts.hasOwnProperty('x.md')); - assert(app.views.posts.hasOwnProperty('y.md')); - assert(app.views.posts.hasOwnProperty('z.md')); - - var docs = app.create('docs'); - - docs.foo({path: 'a.md', content: '...'}); - docs.bar({path: 'b.md', content: '...'}); - docs.baz({path: 'c.md', content: '...'}) - .a({path: 'x.md', content: '...'}) - .b({path: 'y.md', content: '...'}) - .c({path: 'z.md', content: '...'}); - - assert(app.views.docs.hasOwnProperty('a.md')); - assert(app.views.docs.hasOwnProperty('b.md')); - assert(app.views.docs.hasOwnProperty('c.md')); - - assert(app.views.docs.hasOwnProperty('x.md')); - assert(app.views.docs.hasOwnProperty('y.md')); - assert(app.views.docs.hasOwnProperty('z.md')); - }); -}); diff --git a/test/app.view.compile.js b/test/app.view.compile.js deleted file mode 100644 index e325c92..0000000 --- a/test/app.view.compile.js +++ /dev/null @@ -1,38 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('app.view.compile', function () { - describe('compile method', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should compile a view:', function () { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with settings:', function () { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile({foo: 'bar'}); - assert(typeof view.fn === 'function'); - }); - - it('should compile a view with isAsync flag:', function () { - var buffer = new Buffer('a b c'); - var view = app.page('a.tmpl', {contents: buffer}) - .compile(true); - assert(typeof view.fn === 'function'); - }); - }); -}); - diff --git a/test/app.view.render.js b/test/app.view.render.js deleted file mode 100644 index fcf3087..0000000 --- a/test/app.view.render.js +++ /dev/null @@ -1,92 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should use helpers to render a view:', function (done) { - var locals = {name: 'Halle'}; - - app.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - var buffer = new Buffer('a <%= upper(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a HALLE b'); - done(); - }); - }); - - it('should support helpers as an array:', function (done) { - var locals = {name: 'Halle'}; - - app.helpers([ - { - lower: function (str) { - return str.toLowerCase(str); - } - } - ]); - - var buffer = new Buffer('a <%= lower(name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { - if (err) return done(err); - - assert(res.contents.toString() === 'a halle b'); - done(); - }); - }); - - it('should support helpers as an object:', function (done) { - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function (prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a <%= prepend("foo ", name) %> b'); - app.page('a.tmpl', {contents: buffer, locals: locals}) - .render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - - it('should use the engine defined on view options:', function (done) { - app.engine('hbs', require('engine-handlebars')); - var locals = {name: 'Halle'}; - - app.helpers({ - prepend: function (prefix, str) { - return prefix + str; - } - }); - - var buffer = new Buffer('a {{prepend "foo " name}} b'); - app.page('a.tmpl', {contents: buffer, locals: locals, options: {engine: 'hbs'}}) - .render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a foo Halle b'); - done(); - }); - }); - }); -}); - diff --git a/test/app.watch.js b/test/app.watch.js deleted file mode 100644 index 698b768..0000000 --- a/test/app.watch.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var fs = require('fs'); -var App = require('..'); -var app; - -describe.skip('watch()', function () { - beforeEach(function () { - app = new App({runtimes: false}); - }); - - it('should watch files and run a task when files change', function (done) { - this.timeout(750); - - var count = 0, watch; - app.task('default', function (cb) { - count++; - cb(); - }); - - app.task('close', function (cb) { - watch.close(); - app.emit('close'); - cb(); - }); - - app.task('watch', function (cb) { - watch = app.watch('test/fixtures/watch/*.txt', ['default', 'close']); - fs.writeFile('test/fixtures/watch/test.txt', 'test', function (err) { - if (err) return cb(err); - app.on('close', cb); - }); - }); - - app.build(['watch'], function (err) { - if (err) return done(err); - assert.equal(count, 1); - done(); - }); - }); -}); diff --git a/test/collection.engines.js b/test/collection.engines.js deleted file mode 100644 index 4c2a619..0000000 --- a/test/collection.engines.js +++ /dev/null @@ -1,177 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var collection, pages; - -describe('collection engines', function() { - beforeEach(function() { - pages = new Views(); - }); - - it('should throw an error when engine name is invalid:', function () { - (function () { - pages.engine(null, {}); - }).should.throw('expected engine ext to be a string or array.'); - }); - - it('should register an engine to the given extension', function () { - pages.engine('hbs', function () {}); - assert(typeof pages.engines['.hbs'] === 'object'); - }); - - it('should set an engine with the given extension', function () { - var hbs = function() {}; - hbs.render = function() {}; - hbs.renderFile = function() {}; - pages.engine('hbs', hbs); - assert(pages.engines['.hbs']); - assert(pages.engines['.hbs'].renderFile); - assert(pages.engines['.hbs'].render); - }); - - it('should get an engine:', function () { - pages.engine('hbs', function () {}); - var hbs = pages.engine('hbs'); - assert(typeof hbs === 'object'); - assert(hbs.hasOwnProperty('render')); - assert(hbs.hasOwnProperty('compile')); - }); - - it('should register multiple engines to the given extension', function () { - pages.engine(['hbs', 'md'], function () {}); - assert(typeof pages.engines['.hbs'] === 'object'); - assert(typeof pages.engines['.md'] === 'object'); - }); -}); - -describe('engines', function () { - beforeEach(function () { - pages = new Views(); - pages.addView('foo.tmpl', {content: 'A <%= letter %> {{= letter }} C'}); - pages.addView('bar.tmpl', {content: 'A <%= letter %> {{ letter }} C'}); - }); - - it('should register an engine:', function () { - pages.engine('a', {render: function () {}}); - pages.engines.should.have.property('.a'); - }); - - it('should use custom delimiters:', function (done) { - pages.engine('tmpl', require('engine-base'), { - delims: ['{{', '}}'] - }); - - pages.render('foo.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should override individual delims values:', function (done) { - pages.engine('tmpl', require('engine-base'), { - interpolate: /\{{([^}]+)}}/g, - evaluate: /\{{([^}]+)}}/g, - escape: /\{{-([^}]+)}}/g - }); - pages.render('bar.tmpl', {letter: 'B'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('A <%= letter %> B C'); - done(); - }); - }); - - it('should get an engine:', function () { - pages.engine('a', { - render: function () {} - }); - var a = pages.engine('a'); - a.should.have.property('render'); - }); -}); - - -describe('engine selection:', function () { - beforeEach(function (done) { - collection = new Views(); - collection.engine('tmpl', require('engine-base')); - collection.engine('hbs', require('engine-handlebars')); - done(); - }); - - it('should get the engine from file extension:', function (done) { - var pages = new Views(); - pages.engine('tmpl', require('engine-base')); - pages.engine('hbs', require('engine-handlebars')); - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the collection:', function (done) { - var posts = new Views({engine: 'hbs'}); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on the view:', function (done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', engine: 'hbs', locals: {a: 'b'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.options:', function (done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', data: {a: 'b'}, options: {engine: 'hbs'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on view.data:', function (done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}, data: {engine: 'hbs'}}) - .render(function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use the engine defined on render locals:', function (done) { - var posts = new Views(); - posts.engine('tmpl', require('engine-base')); - posts.engine('hbs', require('engine-handlebars')); - posts.addView('a', {content: '{{a}}', locals: {a: 'b'}}) - .render({engine: 'hbs'}, function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); -}); diff --git a/test/collection.events.js b/test/collection.events.js deleted file mode 100644 index e919835..0000000 --- a/test/collection.events.js +++ /dev/null @@ -1,27 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection events', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function () { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var events = []; - - app.pages.on('option', function (key) { - events.push(key); - }); - - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test/collection.js b/test/collection.js deleted file mode 100644 index 2ddee92..0000000 --- a/test/collection.js +++ /dev/null @@ -1,537 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var isBuffer = require('is-buffer'); - -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var Collection = App.Collection; -var collection; - -describe('collection', function () { - describe('constructor', function () { - it('should create an instance of Collection', function () { - var collection = new Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - - it('should instantiate without new', function () { - var collection = Collection(); - assert(collection instanceof Collection); - assert(typeof collection === 'object'); - }); - }); - - describe('static methods', function () { - it('should expose `extend`', function () { - assert(typeof Collection.extend ==='function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - collection = new Collection(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function (method) { - it('should expose ' + method + ' method', function () { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function () { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function () { - assert(Array.isArray(collection.queue)); - }); - - it('should expose items property', function () { - assert(typeOf(collection.items) === 'object'); - }); - - it('should expose options property', function () { - assert(typeOf(collection.options) === 'object'); - }); - }); -}); - -describe('methods', function () { - beforeEach(function() { - collection = new Collection(); - }); - - describe('chaining', function () { - it('should allow collection methods to be chained', function () { - collection - .addItems({'a.hbs': {path: 'a.hbs'}}) - .addItems({'b.hbs': {path: 'b.hbs'}}) - .addItems({'c.hbs': {path: 'c.hbs'}}); - - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - }); - - describe('use', function () { - it('should expose the instance to plugins', function () { - collection - .use(function (inst) { - inst.foo = 'bar'; - }); - - assert(collection.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function () { - collection - .use(function () { - return function (item) { - item.foo = 'bar'; - }; - }); - - collection.addItem('aaa'); - collection.addItem('bbb'); - collection.addItem('ccc'); - - assert(collection.items.aaa.foo === 'bar'); - assert(collection.items.bbb.foo === 'bar'); - assert(collection.items.ccc.foo === 'bar'); - }); - }); - - describe('get / set', function () { - it('should set a value on the instance', function () { - collection.set('a', 'b'); - assert(collection.a ==='b'); - }); - - it('should get a value from the instance', function () { - collection.set('a', 'b'); - assert(collection.get('a') ==='b'); - }); - }); - - describe('adding items', function () { - beforeEach(function () { - collection = new Collection(); - }); - - it('should load a item onto the respective collection', function () { - collection.addItem('a.hbs'); - collection.items.should.have.property('a.hbs'); - }); - }); - - describe('item', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should return a single collection item from a key-value pair', function () { - var one = collection.item('one', {content: 'foo'}); - var two = collection.item('two', {content: 'bar'}); - - assert(one instanceof Item); - assert(one instanceof collection.Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two instanceof collection.Item); - assert(two.path === 'two'); - }); - - it('should return a single collection item from an object', function () { - var one = collection.item({path: 'one', content: 'foo'}); - var two = collection.item({path: 'two', content: 'bar'}); - - assert(one instanceof Item); - assert(one.path === 'one'); - assert(two instanceof Item); - assert(two.path === 'two'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should throw an error when args are invalid', function () { - (function () { - collection.addItem(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a item to `items`', function () { - collection.addItem('foo'); - collection.items.should.have.property('foo'); - - collection.addItem('one', {content: '...'}); - assert(typeof collection.items.one === 'object'); - assert(isBuffer(collection.items.one.contents)); - }); - - it('should create an instance of `Item`', function () { - collection.addItem('one', {content: '...'}); - assert(collection.items.one instanceof collection.Item); - }); - - it('should allow an `Item` constructor to be passed', function () { - Item.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Collection({Item: Item}); - collection.addItem('one', {content: '...'}); - collection.items.one.foo('bar', 'baz'); - assert(collection.items.one.bar === 'baz'); - }); - - it('should allow an instance of `Item` to be passed', function () { - var collection = new Collection({Item: Item}); - var item = new Item({content: '...'}); - collection.addItem('one', item); - item.set('abc', 'xyz'); - assert(collection.items.one instanceof collection.Item); - assert(isBuffer(collection.items.one.contents)); - assert(collection.items.one.abc === 'xyz'); - }); - }); - - describe('addItems', function () { - it('should add multiple items', function () { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should create items from an instance of Collection', function () { - collection.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Collection(collection); - assert(isBuffer(pages.items.one.contents)); - assert(isBuffer(pages.items.two.contents)); - }); - - it('should add an array of plain-objects', function () { - collection.addItems([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add an array of items', function () { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection.addItems(list.items); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should add a list of items', function () { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should add a list of items from the constructor', function () { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Collection(list); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.items.two.contents)); - }); - - it('should throw an error when list is not an array', function () { - var items = new Collection(); - (function () { - items.addList(); - }).should.throw('expected list to be an array.'); - - (function () { - items.addList({}); - }).should.throw('expected list to be an array.'); - - (function () { - items.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event', function () { - var collection = new Collection(); - - collection.on('addList', function (list) { - while (list.length) { - collection.addItem({path: list.pop()}); - } - }); - - collection.addList(['a.txt', 'b.txt', 'c.txt']); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function () { - var collection = new Collection(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { - return {path: fp}; - }); - assert(collection.items.hasOwnProperty('a.txt')); - assert(collection.items['a.txt'].path === 'a.txt'); - }); - - it('should load an object of items from an event', function () { - var collection = new Collection(); - - collection.on('addItems', function (items) { - for (var key in items) { - collection.addItem('foo/' + key, items[key]); - delete items[key]; - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addItems)', function () { - var collection = new Collection(); - - collection.on('addItems', function (items) { - for (var key in items) { - if (key === 'c') { - collection.loaded = true; - break; - } - collection.addItem('foo/' + key, items[key]); - } - }); - - collection.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(!collection.items.hasOwnProperty('foo/c')); - assert(collection.items['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished (addList)', function () { - var collection = new Collection(); - - collection.on('addList', function (items) { - for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item.key === 'c') { - collection.loaded = true; - break; - } - item.key = 'foo/' + item.key; - collection.addItem(item.key, item); - } - }); - - collection.addList([ - {key: 'a', path: 'a.txt'}, - {key: 'b', path: 'b.txt'}, - {key: 'c', path: 'c.txt'} - ]); - - assert(collection.items.hasOwnProperty('foo/a')); - assert(collection.items['foo/a'].path === 'a.txt'); - assert(!collection.items.hasOwnProperty('foo/c')); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - collection = new Collection(); - }); - it('should get a item from `items`', function () { - collection.addItem('one', {content: 'aaa'}); - collection.addItem('two', {content: 'zzz'}); - assert(isBuffer(collection.items.one.contents)); - assert(isBuffer(collection.getItem('one').contents)); - assert(collection.getItem('one').contents.toString() === 'aaa'); - assert(collection.getItem('two').contents.toString() === 'zzz'); - }); - }); -}); - -describe('queue', function () { - beforeEach(function () { - collection = new Collection(); - }); - - it('should emit arguments on addItem', function (done) { - collection.on('addItem', function (args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function () { - collection.queue.push(collection.item('b', {path: 'b'})); - - collection.addItem('a', {path: 'a'}); - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - }); - - it('should load all items on the queue when addItem is called', function () { - collection.on('addItem', function (args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addItem('a.html', 'aaa'); - collection.addItem('b.html', 'bbb'); - collection.addItem('c.html', 'ccc'); - - assert(collection.items.hasOwnProperty('a.html')); - assert(collection.getItem('a.html').content === 'aaa'); - assert(collection.items.hasOwnProperty('b.html')); - assert(collection.getItem('b.html').content === 'bbb'); - assert(collection.items.hasOwnProperty('c.html')); - assert(collection.getItem('c.html').content === 'ccc'); - }); -}); - -describe('options', function() { - describe('option', function() { - beforeEach(function() { - collection = new Collection(); - }); - - it('should expose the `option` method', function () { - collection.option('foo', 'bar'); - collection.options.should.have.property('foo', 'bar'); - }); - - it('should be chainable', function () { - collection.option('foo', 'bar') - .addItems('a.hbs') - .addItems('b.hbs') - .addItems('c.hbs'); - - collection.options.should.have.property('foo', 'bar'); - collection.items.should.have.properties([ - 'a.hbs', - 'b.hbs', - 'c.hbs' - ]); - }); - - it('should set a key/value pair on options', function () { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options', function () { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option', function () { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Collection({ - renameKey: function (key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on item keys', function() { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.items['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the renamed key', function () { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a item with the original key', function () { - collection.addItem('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getItem('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - diff --git a/test/collection.options.js b/test/collection.options.js deleted file mode 100644 index 4994fd4..0000000 --- a/test/collection.options.js +++ /dev/null @@ -1,25 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('collection.option()', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function () { - app.pages.options.should.not.have.property('foo'); - app.pages.option('foo', 'bar'); - app.pages.options.should.have.property('foo'); - }); - - it('should extend options:', function () { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - app.pages.option('a', 'b'); - app.pages.option('c', 'd'); - app.pages.option('e', 'f'); - app.pages.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test/collection.render.js b/test/collection.render.js deleted file mode 100644 index d7c1579..0000000 --- a/test/collection.render.js +++ /dev/null @@ -1,138 +0,0 @@ -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var pages; - -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { - pages = new Views(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function () { - (function() { - pages.render({}); - }).should.throw('Views#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function (done) { - pages.addView('foo.bar', {content: '<%= name %>'}); - var page = pages.getView('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'Views#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a view:', function (done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a view:', function (done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addView('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var view = pages.getView('a.tmpl'); - - pages.render(view, function (err, view) { - if (err) return done(err); - assert(view.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a view from its path:', function (done) { - pages.addView('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function (err, view) { - if (err) return done(err); - assert(view.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function (done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addViews({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, - }); - - pages.use(function (collection) { - collection.option('pager', false); - - collection.renderEach = function (cb) { - var list = new List(collection); - - async.map(list.items, function (item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function (err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/collection.use.js b/test/collection.use.js deleted file mode 100644 index bc1c045..0000000 --- a/test/collection.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Collection = App.Collection; -var Item = App.Item; -var collection; - -describe('collection.use', function () { - beforeEach(function () { - collection = new Collection(); - }); - - it('should expose the instance to `use`:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should be chainable:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Collection); - }) - .use(function (inst) { - assert(inst instanceof Collection); - }) - .use(function (inst) { - assert(inst instanceof Collection); - done(); - }); - }); - - it('should expose the collection to a plugin:', function () { - collection.use(function (items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }); - - collection.foo('a', {content: '...'}); - assert(collection.items.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function () { - collection - .use(function (items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function () { - collection = new Collection({Item: require('vinyl')}); - collection - .use(function (items) { - assert(items instanceof Collection); - items.foo = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof Collection); - items.bar = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof Collection); - items.baz = items.addItem.bind(items); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - }); - - it('should pass to item `use` if a function is returned:', function () { - collection.use(function (items) { - assert(items instanceof Collection); - - return function (item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); - - it('should be chainable when a item function is returned:', function () { - collection - .use(function (items) { - assert(items instanceof Collection); - - return function (item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function (items) { - assert(items instanceof Collection); - - return function (item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function (items) { - assert(items instanceof Collection); - - return function (item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - collection.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.items.hasOwnProperty('a')); - assert(collection.items.hasOwnProperty('b')); - assert(collection.items.hasOwnProperty('c')); - assert(collection.items.hasOwnProperty('d')); - }); -}); diff --git a/test/fixtures/bom-utf16be.txt b/test/fixtures/bom-utf16be.txt deleted file mode 100644 index b9dce78..0000000 Binary files a/test/fixtures/bom-utf16be.txt and /dev/null differ diff --git a/test/fixtures/bom-utf16le.txt b/test/fixtures/bom-utf16le.txt deleted file mode 100644 index 07cc600..0000000 Binary files a/test/fixtures/bom-utf16le.txt and /dev/null differ diff --git a/test/fixtures/bom-utf8.txt b/test/fixtures/bom-utf8.txt deleted file mode 100644 index 42729bc..0000000 --- a/test/fixtures/bom-utf8.txt +++ /dev/null @@ -1 +0,0 @@ -This file is saved as UTF-8 with BOM. 𝌆 diff --git a/test/fixtures/copy/example.txt b/test/fixtures/copy/example.txt deleted file mode 100644 index 6769dd6..0000000 --- a/test/fixtures/copy/example.txt +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/test/fixtures/data/a.json b/test/fixtures/data/a.json deleted file mode 100644 index 8470de4..0000000 --- a/test/fixtures/data/a.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "one": {"a": "aaa"} -} \ No newline at end of file diff --git a/test/fixtures/data/alert.json b/test/fixtures/data/alert.json deleted file mode 100644 index 31767e6..0000000 --- a/test/fixtures/data/alert.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "success": { - "test": true, - "strong": "Heads up! This is a warning!", - "text": "You forgot a field!" - } -} \ No newline at end of file diff --git a/test/fixtures/data/b.json b/test/fixtures/data/b.json deleted file mode 100644 index 5f2fde7..0000000 --- a/test/fixtures/data/b.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "two": {"b": "bbb"} -} \ No newline at end of file diff --git a/test/fixtures/data/c.json b/test/fixtures/data/c.json deleted file mode 100644 index 7c274f3..0000000 --- a/test/fixtures/data/c.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "three": {"c": "ccc"} -} \ No newline at end of file diff --git a/test/fixtures/data/data.json b/test/fixtures/data/data.json deleted file mode 100644 index 7cd0452..0000000 --- a/test/fixtures/data/data.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "root": "Whoa, I should be at the root!" -} \ No newline at end of file diff --git a/test/fixtures/data/test.json b/test/fixtures/data/test.json deleted file mode 100644 index c8e407b..0000000 --- a/test/fixtures/data/test.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "alpha": "one", - "beta": "two" -} \ No newline at end of file diff --git a/test/fixtures/def-gen.js b/test/fixtures/def-gen.js new file mode 100644 index 0000000..9955ea4 --- /dev/null +++ b/test/fixtures/def-gen.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default'); + cb(); + }); +}; diff --git a/test/fixtures/example.txt b/test/fixtures/example.txt deleted file mode 100644 index a8a9406..0000000 --- a/test/fixtures/example.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test \ No newline at end of file diff --git a/test/fixtures/front-matter/autodetect-no-lang.md b/test/fixtures/front-matter/autodetect-no-lang.md deleted file mode 100644 index 99d7221..0000000 --- a/test/fixtures/front-matter/autodetect-no-lang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: autodetect-no-lang -user: jonschlinkert ---- -Content \ No newline at end of file diff --git a/test/fixtures/front-matter/autodetect-yaml.md b/test/fixtures/front-matter/autodetect-yaml.md deleted file mode 100644 index 12eb768..0000000 --- a/test/fixtures/front-matter/autodetect-yaml.md +++ /dev/null @@ -1,5 +0,0 @@ ----yaml -title: autodetect-yaml -user: jonschlinkert ---- -Content \ No newline at end of file diff --git a/test/fixtures/front-matter/lang-yaml.md b/test/fixtures/front-matter/lang-yaml.md deleted file mode 100644 index 414d639..0000000 --- a/test/fixtures/front-matter/lang-yaml.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: YAML ---- - -# This page has YAML front matter! diff --git a/test/fixtures/generic/run.dmc b/test/fixtures/generic/run.dmc deleted file mode 100644 index f5925c0..0000000 --- a/test/fixtures/generic/run.dmc +++ /dev/null @@ -1 +0,0 @@ -# run.dmc \ No newline at end of file diff --git a/test/fixtures/generic/test.dmc b/test/fixtures/generic/test.dmc deleted file mode 100644 index 2286d5d..0000000 --- a/test/fixtures/generic/test.dmc +++ /dev/null @@ -1 +0,0 @@ -# test.dmc \ No newline at end of file diff --git a/test/fixtures/helpers/a.js b/test/fixtures/helpers/a.js deleted file mode 100644 index 44919f5..0000000 --- a/test/fixtures/helpers/a.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function aaa() { - -}; \ No newline at end of file diff --git a/test/fixtures/helpers/b.js b/test/fixtures/helpers/b.js deleted file mode 100644 index 73142e0..0000000 --- a/test/fixtures/helpers/b.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function bbb() { - -}; \ No newline at end of file diff --git a/test/fixtures/helpers/c.js b/test/fixtures/helpers/c.js deleted file mode 100644 index ae7c357..0000000 --- a/test/fixtures/helpers/c.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function ccc() { - -}; \ No newline at end of file diff --git a/test/fixtures/helpers/obj.js b/test/fixtures/helpers/obj.js deleted file mode 100644 index a139721..0000000 --- a/test/fixtures/helpers/obj.js +++ /dev/null @@ -1,9 +0,0 @@ -exports.one = function one() { - -}; -exports.two = function two() { - -}; -exports.three = function three() { - -}; \ No newline at end of file diff --git a/test/fixtures/noext/license b/test/fixtures/noext/license deleted file mode 100644 index 65f90ac..0000000 --- a/test/fixtures/noext/license +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015, Jon Schlinkert. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/test/fixtures/not-exposed.js b/test/fixtures/not-exposed.js new file mode 100644 index 0000000..5cee793 --- /dev/null +++ b/test/fixtures/not-exposed.js @@ -0,0 +1,8 @@ +'use strict'; + +var Base = require('../..'); +var base = new Base({isApp: true}); + +base.register('not-exposed', function(app) { + +}); diff --git a/test/fixtures/one/package.json b/test/fixtures/one/package.json new file mode 100644 index 0000000..42509ea --- /dev/null +++ b/test/fixtures/one/package.json @@ -0,0 +1,9 @@ +{ + "name": "update-one", + "version": "0.0.0", + "private": true, + "description": "", + "main": "updatefile.js", + "license": "MIT", + "base": {} +} diff --git a/test/fixtures/one/templates/a.txt b/test/fixtures/one/templates/a.txt new file mode 100644 index 0000000..2ff8250 --- /dev/null +++ b/test/fixtures/one/templates/a.txt @@ -0,0 +1 @@ +one: aaa \ No newline at end of file diff --git a/test/fixtures/one/templates/x.txt b/test/fixtures/one/templates/x.txt new file mode 100644 index 0000000..139e1d8 --- /dev/null +++ b/test/fixtures/one/templates/x.txt @@ -0,0 +1 @@ +one: xxx \ No newline at end of file diff --git a/test/fixtures/one/templates/y.txt b/test/fixtures/one/templates/y.txt new file mode 100644 index 0000000..7308ea9 --- /dev/null +++ b/test/fixtures/one/templates/y.txt @@ -0,0 +1 @@ +one: yyy \ No newline at end of file diff --git a/test/fixtures/one/templates/z.txt b/test/fixtures/one/templates/z.txt new file mode 100644 index 0000000..04c378a --- /dev/null +++ b/test/fixtures/one/templates/z.txt @@ -0,0 +1 @@ +one: zzz \ No newline at end of file diff --git a/test/fixtures/one/updatefile.js b/test/fixtures/one/updatefile.js new file mode 100644 index 0000000..eb51b6d --- /dev/null +++ b/test/fixtures/one/updatefile.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; diff --git a/test/fixtures/one/verbfile.js b/test/fixtures/one/verbfile.js new file mode 100644 index 0000000..546d004 --- /dev/null +++ b/test/fixtures/one/verbfile.js @@ -0,0 +1,19 @@ +'use strict'; + + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/package.json b/test/fixtures/package.json new file mode 100644 index 0000000..543ca49 --- /dev/null +++ b/test/fixtures/package.json @@ -0,0 +1,9 @@ +{ + "name": "update-tests", + "version": "0.0.0", + "private": true, + "description": "", + "main": "index.js", + "license": "MIT", + "base": {} +} diff --git a/test/fixtures/pipeline/a.js b/test/fixtures/pipeline/a.js deleted file mode 100644 index 3361998..0000000 --- a/test/fixtures/pipeline/a.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - str += 'aaa\n'; - - // var err = new Error('foo'); - // err.plugin = 'a'; - // this.emit('error', err); - // return cb(err); - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test/fixtures/pipeline/b.js b/test/fixtures/pipeline/b.js deleted file mode 100644 index 5f69bb6..0000000 --- a/test/fixtures/pipeline/b.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - str += 'bbb\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test/fixtures/pipeline/c.js b/test/fixtures/pipeline/c.js deleted file mode 100644 index 84a1706..0000000 --- a/test/fixtures/pipeline/c.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - str += 'ccc\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test/fixtures/pipeline/d.js b/test/fixtures/pipeline/d.js deleted file mode 100644 index 79a5dfb..0000000 --- a/test/fixtures/pipeline/d.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -var through = require('through2'); - -module.exports = function(options) { - return through.obj(function (file, enc, cb) { - var str = file.contents.toString(); - str += 'ddd\n'; - - file.contents = new Buffer(str); - this.push(file); - cb(); - }); -}; diff --git a/test/fixtures/posts/a.txt b/test/fixtures/posts/a.txt deleted file mode 100644 index bca29ee..0000000 --- a/test/fixtures/posts/a.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: AAA ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/b.txt b/test/fixtures/posts/b.txt deleted file mode 100644 index 1e128c7..0000000 --- a/test/fixtures/posts/b.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: BBB ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/posts/c.txt b/test/fixtures/posts/c.txt deleted file mode 100644 index 32f9187..0000000 --- a/test/fixtures/posts/c.txt +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: CCC ---- -This is <%= title %> \ No newline at end of file diff --git a/test/fixtures/templates/a.tmpl b/test/fixtures/templates/a.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/a.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/b.tmpl b/test/fixtures/templates/b.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/b.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/templates/c.tmpl b/test/fixtures/templates/c.tmpl deleted file mode 100644 index 36f1f1b..0000000 --- a/test/fixtures/templates/c.tmpl +++ /dev/null @@ -1 +0,0 @@ -<%= name %> \ No newline at end of file diff --git a/test/fixtures/test-symlink b/test/fixtures/test-symlink deleted file mode 120000 index 3fcfe6c..0000000 --- a/test/fixtures/test-symlink +++ /dev/null @@ -1 +0,0 @@ -test.coffee \ No newline at end of file diff --git a/test/fixtures/test-symlink-dir/suchempty b/test/fixtures/test-symlink-dir/suchempty deleted file mode 100644 index 65bbcaa..0000000 --- a/test/fixtures/test-symlink-dir/suchempty +++ /dev/null @@ -1 +0,0 @@ -suchempty \ No newline at end of file diff --git a/test/fixtures/test.coffee b/test/fixtures/test.coffee deleted file mode 100644 index 6769dd6..0000000 --- a/test/fixtures/test.coffee +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/test/fixtures/updater-foo/updatefile.js b/test/fixtures/updater-foo/updatefile.js new file mode 100644 index 0000000..887c058 --- /dev/null +++ b/test/fixtures/updater-foo/updatefile.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/updater.js b/test/fixtures/updater.js new file mode 100644 index 0000000..de1c362 --- /dev/null +++ b/test/fixtures/updater.js @@ -0,0 +1,49 @@ +'use strict'; + +module.exports = function(app) { + app.register('updater-aaa', function(app) { + app.task('default', function(cb) { + console.log('update > default'); + cb(); + }); + + app.register('sub', function(sub) { + sub.task('default', function(cb) { + console.log('aaa > sub > default'); + cb(); + }); + + sub.register('bbb', function(bbb) { + bbb.task('default', function(cb) { + console.log('aaa > sub > bbb > default'); + cb(); + }); + }); + }); + }); + + app.register('updater-abc', 'test/fixtures/generators/a/generator.js'); + + app.register('updater-bbb', function(app) { + app.task('default', function(cb) { + app.update('aaa.sub.bbb', 'default', cb); + }); + }); + + app.register('updater-ccc', function(app) { + app.task('default', function(cb) { + app.update('abc', 'default', cb); + }); + }); + + app.register('updater-ddd', function(app) { + app.task('default', function(cb) { + app.update('abc.docs', 'x', cb); + }); + }); + + app.update('aaa.sub', ['default'], function(err) { + if (err) throw err; + console.log('done'); + }); +}; diff --git a/test/fixtures/updaters/a.txt b/test/fixtures/updaters/a.txt deleted file mode 100644 index 7c4a013..0000000 --- a/test/fixtures/updaters/a.txt +++ /dev/null @@ -1 +0,0 @@ -aaa \ No newline at end of file diff --git a/test/fixtures/updaters/a/.gitignore b/test/fixtures/updaters/a/.gitignore new file mode 100644 index 0000000..80a228c --- /dev/null +++ b/test/fixtures/updaters/a/.gitignore @@ -0,0 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components +node_modules +npm-debug.log +actual +test/actual +temp +tmp +TODO.md +vendor +.idea +benchmark +coverage diff --git a/test/fixtures/updaters/a/package.json b/test/fixtures/updaters/a/package.json new file mode 100644 index 0000000..b9bab76 --- /dev/null +++ b/test/fixtures/updaters/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "updater-a", + "private": true, + "version": "0.1.0", + "files": ["index.js"], + "main": "updatefile.js" +} diff --git a/test/fixtures/updaters/a/post.hbs b/test/fixtures/updaters/a/post.hbs new file mode 100644 index 0000000..b2cb52b --- /dev/null +++ b/test/fixtures/updaters/a/post.hbs @@ -0,0 +1,5 @@ +--- +title: Post +--- + +This is SOME POST \ No newline at end of file diff --git a/test/fixtures/updaters/a/updatefile.js b/test/fixtures/updaters/a/updatefile.js new file mode 100644 index 0000000..0198ef6 --- /dev/null +++ b/test/fixtures/updaters/a/updatefile.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/updaters/a/verbfile.js b/test/fixtures/updaters/a/verbfile.js new file mode 100644 index 0000000..c3f4d75 --- /dev/null +++ b/test/fixtures/updaters/a/verbfile.js @@ -0,0 +1,10 @@ +'use strict'; + +var path = require('path'); + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/updaters/b.txt b/test/fixtures/updaters/b.txt deleted file mode 100644 index 01f02e3..0000000 --- a/test/fixtures/updaters/b.txt +++ /dev/null @@ -1 +0,0 @@ -bbb \ No newline at end of file diff --git a/test/fixtures/updaters/c.txt b/test/fixtures/updaters/c.txt deleted file mode 100644 index 2383bd5..0000000 --- a/test/fixtures/updaters/c.txt +++ /dev/null @@ -1 +0,0 @@ -ccc \ No newline at end of file diff --git a/test/fixtures/updaters/qux/package.json b/test/fixtures/updaters/qux/package.json new file mode 100644 index 0000000..f160d17 --- /dev/null +++ b/test/fixtures/updaters/qux/package.json @@ -0,0 +1,7 @@ +{ + "name": "updater-qux", + "private": true, + "version": "0.1.0", + "files": ["updatefile.js"], + "main": "updatefile.js" +} diff --git a/test/fixtures/updaters/qux/updatefile.js b/test/fixtures/updaters/qux/updatefile.js new file mode 100644 index 0000000..887c058 --- /dev/null +++ b/test/fixtures/updaters/qux/updatefile.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/updaters/qux/verbfile.js b/test/fixtures/updaters/qux/verbfile.js new file mode 100644 index 0000000..28979ee --- /dev/null +++ b/test/fixtures/updaters/qux/verbfile.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * testing... + */ + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/vinyl/bom-utf16be.txt b/test/fixtures/vinyl/bom-utf16be.txt deleted file mode 100644 index b9dce78..0000000 Binary files a/test/fixtures/vinyl/bom-utf16be.txt and /dev/null differ diff --git a/test/fixtures/vinyl/bom-utf16le.txt b/test/fixtures/vinyl/bom-utf16le.txt deleted file mode 100644 index 07cc600..0000000 Binary files a/test/fixtures/vinyl/bom-utf16le.txt and /dev/null differ diff --git a/test/fixtures/vinyl/bom-utf8.txt b/test/fixtures/vinyl/bom-utf8.txt deleted file mode 100644 index 42729bc..0000000 --- a/test/fixtures/vinyl/bom-utf8.txt +++ /dev/null @@ -1 +0,0 @@ -This file is saved as UTF-8 with BOM. 𝌆 diff --git a/test/fixtures/vinyl/test-symlink b/test/fixtures/vinyl/test-symlink deleted file mode 120000 index 3fcfe6c..0000000 --- a/test/fixtures/vinyl/test-symlink +++ /dev/null @@ -1 +0,0 @@ -test.coffee \ No newline at end of file diff --git a/test/fixtures/vinyl/test-symlink-dir b/test/fixtures/vinyl/test-symlink-dir deleted file mode 120000 index 9a17374..0000000 --- a/test/fixtures/vinyl/test-symlink-dir +++ /dev/null @@ -1 +0,0 @@ -wow \ No newline at end of file diff --git a/test/fixtures/vinyl/test.coffee b/test/fixtures/vinyl/test.coffee deleted file mode 100644 index 6769dd6..0000000 --- a/test/fixtures/vinyl/test.coffee +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/test/fixtures/vinyl/wow/suchempty b/test/fixtures/vinyl/wow/suchempty deleted file mode 100644 index 65bbcaa..0000000 --- a/test/fixtures/vinyl/wow/suchempty +++ /dev/null @@ -1 +0,0 @@ -suchempty \ No newline at end of file diff --git a/test/fixtures/watch/test.txt b/test/fixtures/watch/test.txt deleted file mode 100644 index 30d74d2..0000000 --- a/test/fixtures/watch/test.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/test/fixtures/wow/suchempty b/test/fixtures/wow/suchempty deleted file mode 100644 index 65bbcaa..0000000 --- a/test/fixtures/wow/suchempty +++ /dev/null @@ -1 +0,0 @@ -suchempty \ No newline at end of file diff --git a/test/group.js b/test/group.js deleted file mode 100644 index 601e91e..0000000 --- a/test/group.js +++ /dev/null @@ -1,140 +0,0 @@ -require('mocha'); -require('should'); - -var assert = require('assert'); -var support = require('./support/'); -assert.containEql = support.containEql; - -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Group = App.Group; -var group; - -describe('group', function () { - describe('constructor', function () { - it('should create an instance of Group:', function () { - var group = new Group(); - assert(group instanceof Group); - }); - - it('should create an instance of Group with default List:', function () { - var group = new Group(); - assert.deepEqual(group.List, List); - }); - - it('should create an instance of Group with custom List:', function () { - function CustomList () { - List.apply(this, arguments); - } - List.extend(CustomList); - var group = new Group({List: CustomList}); - assert.deepEqual(group.List, CustomList); - }); - }); - - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof Group.extend ==='function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - group = new Group(); - }); - - it('should expose `use`', function () { - assert(typeof group.use ==='function'); - }); - it('should expose `set`', function () { - assert(typeof group.set ==='function'); - }); - it('should expose `get`', function () { - assert(typeof group.get ==='function'); - }); - it('should expose `visit`', function () { - assert(typeof group.visit ==='function'); - }); - it('should expose `define`', function () { - assert(typeof group.define ==='function'); - }); - }); - - describe('instance', function () { - beforeEach(function() { - group = new Group(); - }); - - it('should expose options:', function () { - assert(typeof group.options === 'object'); - }); - - it('should set a value on the instance:', function () { - group.set('a', 'b'); - assert(group.a ==='b'); - }); - - it('should get a value from the instance:', function () { - group.set('a', 'b'); - assert(group.get('a') ==='b'); - }); - }); - - describe('get', function() { - it('should get a normal value when not an array', function () { - var group = new Group({'foo': {items: [1,2,3]}}); - assert.deepEqual(group.get('foo'), {items: [1,2,3]}); - }); - - it('should get an instance of List when value is an array', function () { - var group = new Group({'foo': {items: [{path: 'one.hbs'},{path: 'two.hbs'}, {path: 'three.hbs'}]}}); - var list = group.get('foo.items'); - assert(list instanceof List); - assert.deepEqual(list.items.length, 3); - }); - - it('should throw an error when trying to use a List method on a non List value', function () { - (function () { - var group = new Group({'foo': {items: [1,2,3]}}); - var foo = group.get('foo'); - foo.paginate(); - }).should.throw('paginate can only be used with an array of `List` items.'); - }); - - it('should not override properties already existing on non List values', function (done) { - var group = new Group({'foo': {items: [1,2,3], paginate: function () { - assert(true); - done(); - }}}); - var foo = group.get('foo'); - foo.paginate(); - }); - }); - - describe('use', function() { - beforeEach(function() { - group = new Group(); - }); - - it('should use middleware on a group:', function () { - group.set('one', {contents: new Buffer('aaa')}); - group.set('two', {contents: new Buffer('zzz')}); - - group - .use(function (group) { - group.options = {}; - }) - .use(function (group) { - group.options.foo = 'bar'; - }) - .use(function () { - this.set('one', 'two'); - }); - - assert(group.one === 'two'); - assert(group.options.foo === 'bar'); - }); - }); -}); - diff --git a/test/handlers.js b/test/handlers.js deleted file mode 100644 index 4c07276..0000000 --- a/test/handlers.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('handlers', function () { - describe('custom handlers', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should add custom middleware handlers:', function () { - app.handler('foo'); - app.handler('bar'); - - app.pages.use(function () { - return function (view) { - app.handle('foo', view); - app.handle('bar', view); - }; - }); - - app.foo(/a/, function (view, next) { - view.one = 'aaa'; - next(); - }); - - app.bar(/z/, function (view, next) { - view.two = 'zzz'; - next(); - }); - - app.pages('a.txt', {content: 'aaa'}); - app.pages('z.txt', {content: 'zzz'}); - - app.pages.getView('a.txt').should.have.property('one'); - app.pages.getView('a.txt').should.not.have.property('two'); - - app.pages.getView('z.txt').should.not.have.property('one'); - app.pages.getView('z.txt').should.have.property('two'); - }); - }); -}); diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index dddc13c..0000000 --- a/test/helpers.js +++ /dev/null @@ -1,708 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var Base = require('base-methods'); -var assert = require('assert'); -var consolidate = require('consolidate'); -var handlebars = require('engine-handlebars'); -var matter = require('parser-front-matter'); -var swig = consolidate.swig; -require('swig'); - -var support = require('./support'); -var App = support.resolve(); -var helpers = App._.proto.helpers; -var init = App._.proto.init; -var app; - -describe('helpers', function () { - describe('constructor', function () { - it('should create an instance of Helpers:', function () { - app = new App(); - assert(app instanceof App); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - app = new App(); - }); - it('should expose `helper`', function () { - assert(typeof app.helper ==='function'); - }); - it('should expose `asyncHelper`', function () { - assert(typeof app.asyncHelper ==='function'); - }); - }); - - describe('instance', function () { - it('should prime _', function () { - function Foo() { - Base.call(this); - init(this); - } - Base.extend(Foo); - var foo = new Foo(); - helpers(foo); - assert(typeof foo._ ==='object'); - }); - }); - - describe('helpers', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add a sync helper to the `sync` object:', function () { - app.helper('one', function () {}); - assert(typeof app._.helpers.sync.one === 'function'); - }); - - it('should load a glob of sync helper functions:', function () { - app.helpers('test/fixtures/helpers/[a-c].js'); - - assert(typeof app._.helpers.sync.c === 'function'); - assert(typeof app._.helpers.sync.b === 'function'); - assert(typeof app._.helpers.sync.a === 'function'); - }); - - it('should fail gracefully on bad globs:', function (done) { - try { - app.helpers('test/fixtures/helpers/*.foo'); - done(); - } catch(err) { - done(new Error('should not throw an error.')); - } - }); - - it('should add a glob of sync helper objects:', function () { - app.helpers('test/fixtures/helpers/!([a-c]).js'); - assert(typeof app._.helpers.sync.one === 'function'); - assert(typeof app._.helpers.sync.two === 'function'); - assert(typeof app._.helpers.sync.three === 'function'); - }); - - it('should add a glob with mixed helper objects and functions:', function () { - app.helpers('test/fixtures/helpers/*.js'); - assert(typeof app._.helpers.sync.a === 'function'); - assert(typeof app._.helpers.sync.b === 'function'); - assert(typeof app._.helpers.sync.c === 'function'); - assert(typeof app._.helpers.sync.one === 'function'); - assert(typeof app._.helpers.sync.two === 'function'); - assert(typeof app._.helpers.sync.three === 'function'); - }); - - it('should add an object of sync helpers to the `sync` object:', function () { - app.helpers({ - x: function () {}, - y: function () {}, - z: function () {} - }); - - assert(typeof app._.helpers.sync.x === 'function'); - assert(typeof app._.helpers.sync.y === 'function'); - assert(typeof app._.helpers.sync.z === 'function'); - }); - - it('should add a helper "group":', function () { - app.helperGroup('foo', { - x: function () {}, - y: function () {}, - z: function () {} - }); - - assert(typeof app._.helpers.sync.foo.x === 'function'); - assert(typeof app._.helpers.sync.foo.y === 'function'); - assert(typeof app._.helpers.sync.foo.z === 'function'); - }); - }); - - describe('async helpers', function() { - beforeEach(function() { - app = new App(); - }); - - it('should add an async helper to the `async` object:', function () { - app.asyncHelper('two', function () {}); - assert(typeof app._.helpers.async.two === 'function'); - }); - - it('should load a glob of async helper functions:', function () { - app.asyncHelpers('test/fixtures/helpers/[a-c].js'); - assert(typeof app._.helpers.async.a === 'function'); - assert(typeof app._.helpers.async.b === 'function'); - assert(typeof app._.helpers.async.c === 'function'); - }); - - it('should add a glob of async helper objects:', function () { - app.asyncHelpers('test/fixtures/helpers/!([a-c]).js'); - assert(typeof app._.helpers.async.one === 'function'); - assert(typeof app._.helpers.async.two === 'function'); - assert(typeof app._.helpers.async.three === 'function'); - }); - - it('should fail gracefully on bad globs:', function (done) { - try { - app.asyncHelpers('test/fixtures/helpers/*.foo'); - done(); - } catch(err) { - done(new Error('should not throw an error.')); - } - }); - - it('should add a glob with mixed helper objects and functions:', function () { - app.asyncHelpers('test/fixtures/helpers/*.js'); - assert(typeof app._.helpers.async.a === 'function'); - assert(typeof app._.helpers.async.b === 'function'); - assert(typeof app._.helpers.async.c === 'function'); - assert(typeof app._.helpers.async.one === 'function'); - assert(typeof app._.helpers.async.two === 'function'); - assert(typeof app._.helpers.async.three === 'function'); - }); - - it('should add an object of async helpers to the `async` object:', function () { - app.asyncHelpers({ - x: function () {}, - y: function () {}, - z: function () {} - }); - - assert(typeof app._.helpers.async.x === 'function'); - assert(typeof app._.helpers.async.y === 'function'); - assert(typeof app._.helpers.async.z === 'function'); - }); - - it('should add an async helper "group":', function () { - app.helperGroup('foo', { - x: function () {}, - y: function () {}, - z: function () {} - }, true); - - assert(typeof app._.helpers.async.foo.x === 'function'); - assert(typeof app._.helpers.async.foo.y === 'function'); - assert(typeof app._.helpers.async.foo.z === 'function'); - }); - }); -}); - -describe('sync helpers', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should register a helper:', function () { - app.helper('a', function () {}); - app.helper('b', function () {}); - assert(app._.helpers.sync.hasOwnProperty('a')); - assert(app._.helpers.sync.hasOwnProperty('b')); - }); - - it('should use a helper:', function (done) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= upper(a) %>', locals: {a: 'bbb'}}); - app.helper('upper', function (str) { - return str.toUpperCase(); - }); - - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - done(); - }); - }); - - it('should use a namespaced helper:', function (done) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= foo.upper(a) %>', locals: {a: 'bbb'}}); - - app.helperGroup('foo', { - upper: function (str) { - return str.toUpperCase(); - } - }); - - // console.log(app._.helpers) - - var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { - if (err) return done(err); - - assert.equal(typeof view.contents.toString(), 'string'); - assert.equal(view.contents.toString(), 'BBB'); - done(); - }); - }); -}); - -describe('async helpers', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - it('should register an async helper:', function () { - app.asyncHelper('a', function () {}); - app.asyncHelper('b', function () {}); - app._.helpers.async.should.have.property('a'); - app._.helpers.async.should.have.property('b'); - }); - - it('should use an async helper:', function (done) { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= lower(a) %>', locals: {a: 'BBB'}}); - app.asyncHelper('lower', function (str, next) { - if (typeof next !== 'function') return str; - next(null, str.toLowerCase()); - }); - - var page = app.pages.getView('a.tmpl'); - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'bbb'); - done(); - }); - }); -}); - -describe('built-in helpers:', function () { - describe('automatically generated helpers for default view types:', function () { - beforeEach(function () { - app = new App({rethrow: false}); - app.engine('md', require('engine-base')); - app.engine('tmpl', require('engine-base')); - app.create('partials', { viewType: 'partial' }); - app.create('pages'); - - // parse front matter - app.onLoad(/./, function (view, next) { - matter.parse(view, next); - }); - }); - - it('should expose front matter to the `partial` helper.', function (done) { - app.partial('a.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('b.md', {path: 'b.md', content: 'foo <%= partial("a.md") %> bar'}); - - app.render('b.md', function (err, res) { - if (err) return done(err); - res.content.should.equal('foo AAA bar'); - done(); - }); - }); - - it('should use helper locals.', function (done) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo CCC bar'); - done(); - }); - }); - - it('should use front matter data.', function (done) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo AAA bar'); - done(); - }); - }); - - it('should use partial locals:', function (done) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo EEE bar'); - done(); - }); - }); - - it('should use locals from the `view.render` method:', function (done) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'EEE'}}); - - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}) - .render({name: 'DDD'}, function (err, res) { - if (err) return done(err); - - res.content.should.equal('foo EEE bar'); - done(); - }); - }); - - it('should use locals from the `app.render` method:', function (done) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo DDD bar'); - done(); - }); - }); - - it('should return an empty string when the partial is missing.', function (done) { - app.partial('abc.md', {content: '---\nname: "AAA"\n---\n<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("def.md", { name: "CCC" }) %> bar'}); - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo bar'); - done(); - }); - }); - }); - - describe('helper context:', function () { - beforeEach(function () { - app = new App({rethrow: false}); - app.engine(['tmpl', 'md'], require('engine-base')); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function (view, next) { - matter.parse(view, next); - }); - }); - - it('should prefer helper locals over view locals.', function (done) { - app.partial('abc.md', {content: '<%= name %>', name: 'BBB'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md", { name: "CCC" }) %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo CCC bar'); - done(); - }); - }); - - it('should give preference to view locals over render locals.', function (done) { - app.partial('abc.md', {content: '<%= name %>', locals: {name: 'BBB'}}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - var page = app.pages.getView('xyz.md'); - - app.render(page, {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo BBB bar'); - done(); - }); - }); - - it('should use render locals when other locals are not defined.', function (done) { - app.partial('abc.md', {content: '<%= name %>'}); - app.page('xyz.md', {path: 'xyz.md', content: 'foo <%= partial("abc.md") %> bar'}); - - app.render('xyz.md', {name: 'DDD'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo DDD bar'); - done(); - }); - }); - }); - - describe('user-defined engines:', function () { - beforeEach(function () { - app = new App({rethrow: false}); - app.create('partial', { viewType: 'partial' }); - app.create('page'); - - // parse front matter - app.onLoad(/./, function (view, next) { - matter.parse(view, next); - }); - }); - - it('should use the `partial` helper with handlebars.', function (done) { - app.engine(['tmpl', 'md'], require('engine-base')); - app.engine('hbs', handlebars); - - app.partial('title.hbs', {content: '{{name}}', locals: {name: 'BBB'}}); - app.page('a.hbs', {path: 'a.hbs', content: 'foo {{{partial "title.hbs" this}}} bar'}); - - app.render('a.hbs', {name: 'Halle Nicole'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('foo Halle Nicole bar'); - done(); - }); - }); - - it('should use the `partial` helper with any engine.', function (done) { - app.engine('hbs', handlebars); - app.engine('md', handlebars); - app.engine('swig', swig); - app.engine('tmpl', require('engine-base')); - - app.partial('a.hbs', {content: '---\nname: "AAA"\n---\n{{name}}', locals: {name: 'BBB'}}); - app.page('a.hbs', {path: 'a.hbs', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('b.tmpl', {path: 'b.tmpl', content: '<%= author %>', locals: {author: 'Halle Nicole'}}); - app.page('d.swig', {path: 'd.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('e.swig', {path: 'e.swig', content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('f.hbs', {content: '{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('g.md', {content: '---\nauthor: Brian Woodward\n---\n{{author}}', locals: {author: 'Halle Nicole'}}); - app.page('with-partial.hbs', {path: 'with-partial.hbs', content: '{{{partial "a.hbs" custom.locals}}}'}); - - var locals = {custom: {locals: {name: 'Halle Nicole' }}}; - app.render('a.hbs', locals, function (err, res) { - if (err) return console.log(err); - res.content.should.equal('Halle Nicole'); - }); - - app.render('with-partial.hbs', locals, function (err, res) { - if (err) return console.log(err); - res.content.should.equal('Halle Nicole'); - }); - - var page = app.pages.getView('g.md'); - locals.author = page.data.author || locals.author; - page.render(locals, function (err, res) { - if (err) return done(err); - res.content.should.equal('Brian Woodward'); - done(null, res.content); - }); - }); - }); -}); - -describe('helpers integration', function () { - beforeEach(function () { - app = new App(); - app.create('pages'); - app.engine('md', require('engine-base')); - }); - - describe('.helpers()', function () { - it('should add helpers and use them in templates.', function (done) { - app.helpers({ - upper: function (str) { - return str.toUpperCase(); - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - }); - - describe('helper options:', function () { - it('should expose `this.options` to helpers:', function (done) { - app.helper('cwd', function (fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('one', 'two'); - app.option('cwd', 'foo/bar'); - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function (err, res) { - if (err) return done(err); - assert(res.content === 'a foo/bar/baz b'); - done(); - }); - }); - - it('should pass helper options to helpers:', function (done) { - app.helper('cwd', function (fp) { - return path.join(this.options.cwd, fp); - }); - - app.option('helper.cwd', 'foo/bar'); - app.option('helper.whatever', '...'); - - app.page('doc.md', {content: 'a <%= cwd("baz") %> b'}) - .render(function (err, res) { - if (err) return done(err); - assert(res.content === 'a foo/bar/baz b'); - done(); - }); - }); - }); - - describe('options.helpers', function () { - it('should register helpers passed on the options:', function (done) { - app.option({ - helpers: { - upper: function (str) { - return str.toUpperCase(); - }, - foo: function (str) { - return 'foo' + str; - } - } - }); - - app.page('doc.md', {content: 'a <%= upper(name) %> <%= foo("bar") %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE foobar b'); - done(); - }); - }); - }); - - describe('options.helpers', function () { - it('should add helpers and use them in templates.', function (done) { - app.options.helpers = { - upper: function (str) { - return str.toUpperCase(); - }, - foo: function (str) { - return 'foo' + str; - } - }; - - app.page('doc.md', {content: 'a <%= upper(name) %> b'}) - .render({name: 'Halle'}, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - }); -}); - -describe('collection helpers', function () { - beforeEach(function () { - app = new App(); - app.create('posts'); - app.create('pages', {engine: 'hbs'}); - app.create('partials', {viewType: 'partial', engine: 'hbs'}); - app.create('snippet', {viewType: 'partial'}); - app.engine('hbs', require('engine-handlebars')); - app.helper('log', function (ctx) { - console.log(ctx); - }); - }); - - describe('plural', function () { - it('should get the given collection', function (done) { - app.post('a.hbs', {content: 'foo'}); - app.post('b.hbs', {content: 'bar'}); - app.post('c.hbs', {content: 'baz'}); - - app.partial('list.hbs', { - content: '{{#posts}}{{#each items}}{{content}}{{/each}}{{/posts}}' - }); - - app.page('index.hbs', { - content: '{{> list.hbs }}' - }) - .render(function (err, res) { - if (err) return done(err); - assert(res.content === 'foobarbaz'); - done(); - }); - }); - }); - - describe('single', function () { - it('should get a view from an unspecified collection', function (done) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - - var one = app.page('one', {content: '{{view "a.hbs"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'post-b'); - done(); - }); - - it('should return an empty string if not found', function (done) { - var one = app.page('one', {content: '{{view "foo.hbs"}}'}) - .compile() - .fn(); - assert(one === ''); - done(); - }); - - it('should handle engine errors', function (done) { - app.post('foo.hbs', {content: '{{one "two"}}'}); - app.page('one', {content: '{{posts "foo.hbs"}}'}) - .render(function (err) { - assert(err); - assert(typeof err === 'object'); - assert(typeof err.message === 'string'); - assert(/Missing helper: "one"/.test(err.message)); - done(); - }); - }); - - it('should handle engine errors2', function(done) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.create('foo', {viewType: 'partial'}); - app.foo('foo.tmpl', {path: 'foo.tmpl', content: '<%= blah.bar %>'}); - app.bar('one.tmpl', {content: '<%= foo("foo.tmpl") %>'}) - .render(function (err) { - assert(err); - assert(typeof err === 'object'); - assert(/blah is not defined/.test(err.message)); - done(); - }); - }); - - it('should work with non-handlebars engine', function (done) { - app.engine('tmpl', require('engine-base')); - app.create('foo', {engine: 'tmpl'}); - app.create('bar', {engine: 'tmpl'}); - - app.foo('a.tmpl', {content: 'foo-a'}); - app.foo('b.tmpl', {content: 'foo-b'}); - - var one = app.bar('one', {content: '<%= view("a.tmpl") %>'}) - .compile() - .fn(); - - var two = app.bar('two', {content: '<%= view("b.tmpl") %>'}) - .compile() - .fn(); - - assert(one === 'foo-a'); - assert(two === 'foo-b'); - done(); - }); - - it('should get a specific view from the given collection', function (done) { - app.post('a.hbs', {content: 'post-a'}); - app.post('b.hbs', {content: 'post-b'}); - app.post('c.hbs', {content: 'post-c'}); - app.page('a.hbs', {content: 'page-a'}); - app.page('b.hbs', {content: 'page-b'}); - app.page('c.hbs', {content: 'page-c'}); - - var one = app.page('one', {content: '{{view "a.hbs" "posts"}}'}) - .compile() - .fn(); - - var two = app.page('two', {content: '{{view "b.hbs" "pages"}}'}) - .compile() - .fn(); - - assert(one === 'post-a'); - assert(two === 'page-b'); - done(); - }); - }); -}); diff --git a/test/item.js b/test/item.js deleted file mode 100644 index c44ea4d..0000000 --- a/test/item.js +++ /dev/null @@ -1,1068 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var assert = require('assert'); -var es = require('event-stream'); -var Stream = require('stream'); -var support = require('./support'); -var App = support.resolve(); -var Item = App.Item; -var item; - -describe('Item', function () { - describe('instance', function () { - it('should create an instance of Item:', function () { - item = new Item(); - assert(item instanceof Item); - }); - - it('should instantiate without new:', function () { - item = Item(); - assert(item instanceof Item); - }); - - it('inspect should not double name `Stream` when ctor is `Stream`', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - done(); - }); - }); - - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof Item.extend === 'function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function () { - item = new Item(); - }); - - it('should expose `set`:', function () { - assert(typeof item.set === 'function'); - }); - it('should expose `get`:', function () { - assert(typeof item.get === 'function'); - }); - it('should expose `del`:', function () { - assert(typeof item.del === 'function'); - }); - it('should expose `define`:', function () { - assert(typeof item.define === 'function'); - }); - it('should expose `visit`:', function () { - assert(typeof item.visit === 'function'); - }); - }); - - describe('properties', function () { - it('should expose an `options` property', function () { - item = new Item({}); - assert.deepEqual(item.options, {}); - assert(item.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function () { - item = new Item({options: {foo: 'bar'}}); - assert(item.options.foo === 'bar'); - }); - - it('should expose a `data` property', function () { - item = new Item({app: {}}); - assert.deepEqual(item.data, {}); - assert(item.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function () { - item = new Item({data: {foo: 'bar'}}); - assert(item.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function () { - item = new Item({locals: {foo: 'bar'}}); - assert(item.locals.foo === 'bar'); - }); - }); - - describe('set', function () { - it('should set properties on the object', function () { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.foo, 'bar'); - }); - }); - - describe('get', function () { - it('should get properties from the object', function () { - item = new Item(); - item.set('foo', 'bar'); - assert.equal(item.get('foo'), 'bar'); - }); - }); - - describe('cwd', function () { - it('should get properties from the object', function () { - item = new Item({cwd: 'test/fixtures'}); - assert(item.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function () { - it('should clone the item:', function () { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), item.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!item.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(item.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function () { - item = new Item({content: 'foo'}); - item.set({path: 'foo/bar'}); - item.set('options.one', 'two'); - var clone = item.clone({deep: true}); - clone.set('options.three', 'four'); - assert(item.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!item.get('options.three')); - }); - }); - - describe('visit', function () { - it('should visit all properties on an object and call the specified method', function () { - item = new Item(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - item.visit('set', obj); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function () { - item = new Item(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - item.visit('set', arr); - assert.equal(item.get('foo'), 'bar'); - assert.equal(item.get('bar'), 'baz'); - assert.equal(item.get('baz'), 'bang'); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in Item, we need - * to ensure that these still pass. - */ - -describe('Item', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - var item = new Item(); - assert(Item.isVinyl(item) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(Item.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - var item = new Item(); - item.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - var item = new Item({cwd: cwd}); - item.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - var item = new Item(); - item.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - var item = new Item(); - should.not.exist(item.path); - done(); - }); - - it('should default history to []', function(done) { - var item = new Item(); - item.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - var item = new Item(); - should.not.exist(item.stat); - done(); - }); - - it('should default contents to null', function(done) { - var item = new Item(); - should.not.exist(item.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - var item = new Item({base: val}); - item.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - var item = new Item({cwd: val}); - item.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - var item = new Item({path: val}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - var item = new Item({history: [val]}); - item.path.should.equal(val); - item.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - var item = new Item({stat: val}); - item.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isBuffer().should.equal(false); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isBuffer().should.equal(false); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isStream().should.equal(false); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isStream().should.equal(false); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val}); - item.isNull().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null}); - item.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var item = new Item({contents: val, stat: fakeStat}); - item.isDirectory().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var item = new Item({contents: null, stat: fakeStat}); - item.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'buffer ref should be different'); - item2.contents.toString('utf8').should.equal(item.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var item = new Item(options); - - var copy1 = item.clone({ contents: false }); - copy1.contents.should.equal(item.contents); - - var copy2 = item.clone({}); - copy2.contents.should.not.equal(item.contents); - - var copy3 = item.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(item.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var item = new Item(options); - var item2 = item.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.contents.should.not.equal(item.contents, 'stream ref should not be the same'); - item.contents.pipe(es.wait(function(err, data) { - item2.contents.pipe(es.wait(function(err, data2) { - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - should.not.exist(item2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - - assert(item.stat.hasOwnProperty('birthtime')); - assert(copy.stat.hasOwnProperty('birthtime')); - assert.deepEqual(item.stat, copy.stat); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var item = new Item(options); - var copy = item.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - item.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - var item2 = item.clone(); - - item2.should.not.equal(item, 'refs should be different'); - item2.cwd.should.equal(item.cwd); - item2.base.should.equal(item.base); - item2.path.should.equal(item.path); - item2.custom.should.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.path = '/test/test.js'; - item.path = '/test/test-938di2s.js'; - var item2 = item.clone(); - - item2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - item2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var item = new Item(options); - item.custom = { a: 'custom property' }; - - var item2 = item.clone(true); - item2.custom.should.eql(item.custom); - item2.custom.should.not.equal(item.custom); - item2.custom.a.should.equal(item.custom.a); - - var item3 = item.clone({ deep: true }); - item3.custom.should.eql(item.custom); - item3.custom.should.not.equal(item.custom); - item3.custom.a.should.equal(item.custom.a); - - var item4 = item.clone(false); - item4.custom.should.eql(item.custom); - item4.custom.should.equal(item.custom); - item4.custom.a.should.equal(item.custom.a); - - var item5 = item.clone({ deep: false }); - item5.custom.should.eql(item.custom); - item5.custom.should.equal(item.custom); - item5.custom.a.should.equal(item.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = item.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - item.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var item = new Item(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = item.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var item = new Item(); - item.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var item = new Item({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete item.base; - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - item.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - item.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var item = new Item(); - item.contents = val; - item.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var item = new Item(); - item.contents = val; - (item.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var item = new Item(); - item.contents = val; - item.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var item = new Item(); - try { - item.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var a; - var item = new Item(); - delete item.base; - try { - a = item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var a; - var item = new Item(); - try { - a = item.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - item.relative.should.equal(path.join('test','test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var a; - var item = new Item(); - try { - a = item.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var item = new Item(); - try { - item.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.dirname = '/test/foo'; - item.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - var a; - var item = new Item(); - try { - a = item.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var item = new Item(); - try { - item.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.basename = 'foo.png'; - item.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - var a; - var item = new Item(); - try { - a = item.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var item = new Item(); - try { - item.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - var item = new Item({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - item.extname = '.png'; - item.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - - it('should record history when instantiation', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.js'; - item.path.should.eql('/test/test.js'); - item.history.should.eql(['/test/test.coffee', '/test/test.js']); - - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var item = new Item({ - cwd: '/', - path: '/test/test.coffee' - }); - - item.path = '/test/test.coffee'; - item.path = '/test/test.coffee'; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - item.path = ''; - item.path.should.eql('/test/test.coffee'); - item.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - new Item({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - var item = new Item({ - cwd: '/', - path: 'foo' - }); - - (function() { - item.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test/layouts.js b/test/layouts.js deleted file mode 100644 index f079d32..0000000 --- a/test/layouts.js +++ /dev/null @@ -1,127 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('layouts', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('layout', { viewType: 'layout' }); - app.create('page'); - }); - - it('should apply a layout to a view:', function (done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should not apply a layout when `layoutApplied` is set:', function (done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - page.option('layoutApplied', true); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'b'); - done(); - }); - }); - - it('should not apply a layout to itself:', function (done) { - app.layout('base', {path: 'base.tmpl', content: 'a {% body %} c', layout: 'base'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'b', layout: 'base'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should apply nested layouts to a view:', function (done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function (done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert(view.layoutStack.length === 4); - assert(typeof view.layoutStack[0] === 'object'); - assert(typeof view.layoutStack[0].depth === 'number'); - done(); - }); - }); - - it('should track layout stack history on `layoutStack`:', function (done) { - app.layout('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.layout('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.layout('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.layout('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); - - it('should get layouts from `layout` viewTypes:', function (done) { - app.create('section', { viewType: 'layout' }); - app.create('block', { viewType: 'layout' }); - - app.section('a', {path: 'a.tmpl', content: 'a {% body %} a', layout: 'b'}); - app.block('b', {path: 'b.tmpl', content: 'b {% body %} b', layout: 'c'}); - app.section('c', {path: 'c.tmpl', content: 'c {% body %} c', layout: 'base'}); - app.block('base', {path: 'base.tmpl', content: 'outter {% body %} outter'}); - - app.pages('z.tmpl', {path: 'a.tmpl', content: 'inner', layout: 'a'}); - var page = app.pages.getView('z.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'outter c b a inner a b c outter'); - done(); - }); - }); -}); diff --git a/test/list.js b/test/list.js deleted file mode 100644 index 0dc23ca..0000000 --- a/test/list.js +++ /dev/null @@ -1,689 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var get = require('get-value'); -var isBuffer = require('is-buffer'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var support = require('./support/'); -assert.containEql = support.containEql; - -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Views = App.Views; -var list, views; - -describe('list', function () { - describe('constructor', function () { - it('should create an instance of List', function () { - var list = new List(); - assert(list instanceof List); - }); - - it('should instaniate without `new`', function () { - var list = List(); - assert(list instanceof List); - }); - }); - - describe('static methods', function () { - it('should expose `extend`', function () { - assert(typeof List.extend ==='function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - list = new List(); - }); - - var methods = [ - 'use', - 'setItem', - 'addItem', - 'addItems', - 'addList', - 'getItem', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function (method) { - it('should expose the ' + method + ' method', function () { - assert(typeof list[method] === 'function'); - }); - }); - - it('should expose the isList property', function () { - assert(typeof list.isList === 'boolean'); - }); - - it('should expose the keys property', function () { - assert(Array.isArray(list.keys)); - }); - - it('should expose the queue property', function () { - assert(Array.isArray(list.queue)); - }); - - it('should expose the items property', function () { - assert(Array.isArray(list.items)); - }); - - it('should expose the options property', function () { - assert(typeOf(list.options) === 'object'); - }); - }); - - describe('instance', function () { - beforeEach(function() { - list = new List(); - }); - - it('should set a value on the instance', function () { - list.set('a', 'b'); - assert(list.a ==='b'); - }); - - it('should get a value from the instance', function () { - list.set('a', 'b'); - assert(list.get('a') ==='b'); - }); - }); - - describe('use', function () { - beforeEach(function() { - list = new List(); - }); - - it('should expose the instance to plugins', function () { - list - .use(function (inst) { - inst.foo = 'bar'; - }); - - assert(list.foo === 'bar'); - }); - - it('should expose `item` when the plugin returns a function', function () { - list - .use(function () { - return function (item) { - item.foo = 'bar'; - }; - }); - - list.addItem('aaa'); - list.addItem('bbb'); - list.addItem('ccc'); - - assert(list.items[0].foo === 'bar'); - assert(list.items[1].foo === 'bar'); - assert(list.items[2].foo === 'bar'); - }); - }); - - describe('addItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add items to a list', function () { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - }); - }); - - describe('removeItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should remove an item from `items`', function () { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - var a = list.getItem('a'); - list.removeItem(a); - assert(list.items.length === 2); - var c = list.getItem(c); - list.removeItem(c); - assert(list.items[0].key === 'b'); - }); - - it('should remove an item from `items` by key', function () { - list.addItem('a', {content: '...'}); - list.addItem('b', {content: '...'}); - list.addItem('c', {content: '...'}); - assert(list.items.length === 3); - list.removeItem('c'); - assert(list.items.length === 2); - list.removeItem('b'); - assert(list.items[0].key === 'a'); - }); - }); - - describe('addItems', function() { - beforeEach(function() { - list = new List(); - }); - - it('should add an object with multiple items', function () { - list.addItems({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should signal `loaded` when finished (addItems)', function () { - list.on('addItems', function (items) { - for (var key in items) { - if (key === 'c') { - list.loaded = true; - break; - } - list.addItem('foo/' + key, items[key]); - } - }); - - list.addItems({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert.equal(list.items.length, 2); - assert.equal(list.items[0].key, 'foo/a'); - assert.equal(list.items[0].path, 'a.txt'); - }); - }); - - describe('addList', function () { - beforeEach(function() { - list = new List(); - }); - - it('should add an array with multiple items', function () { - list.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - - it('should take a callback on `addList`', function () { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - ], addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }); - - it('should throw an error when the list is not an array', function () { - function addContents(item) { - item.contents = new Buffer(item.path.charAt(0)); - } - - (function () { - list.addList({ - 'a.md': {locals: { date: '2014-01-01', foo: 'zzz', bar: 1 }}, - 'f.md': {locals: { date: '2014-01-01', foo: 'mmm', bar: 2 }}, - 'd.md': {locals: { date: '2014-01-01', foo: 'xxx', bar: 3 }}, - }, addContents); - - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - assert(isBuffer(list.items[2].contents)); - }).should.throw('expected list to be an array.'); - }); - - it('should signal `loaded` when finished (addList)', function () { - list.on('addList', function (items) { - var len = items.length, i = -1; - while (++i < len) { - if (items[i].path === 'd.md') { - list.loaded = true; - break; - } - list.addItem('foo/' + items[i].path, items[i]); - } - }); - - list.addList([ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - ]); - - assert.equal(list.items.length, 2); - assert.equal(list.keys.indexOf('d.md'), -1); - }); - }); - - describe('queue', function () { - beforeEach(function () { - list = new List(); - }); - - it('should emit arguments on addItem', function (done) { - list.on('addItem', function (args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - list.addItem('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading items', function () { - list.queue.push(list.item('b', {path: 'b'})); - - list.addItem('a', {path: 'a'}); - assert(list.items[0].key === 'a'); - assert(list.items[1].key === 'b'); - }); - - it('should load all items on the queue when addItem is called', function () { - list.on('addItem', function (args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - list.addItem('a.html', 'aaa'); - list.addItem('b.html', 'bbb'); - list.addItem('c.html', 'ccc'); - - assert(list.items[0].path === 'a.html'); - assert(list.getItem('a.html').content === 'aaa'); - assert(list.items[1].path === 'b.html'); - assert(list.getItem('b.html').content === 'bbb'); - assert(list.items[2].path === 'c.html'); - assert(list.getItem('c.html').content === 'ccc'); - }); - }); - - describe('sortBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - ]; - - it('should sort a list', function () { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function (a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should not sort the (original) instance list `items`', function () { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function (a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - // should not be sorted - assert.containEql(list.items, items); - - // should be sorted - assert.containEql(res.items, [ - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } - ]); - }); - - it('should pass options to array-sort from the constructor', function () { - list = new List({sort: {reverse: true}}); - list.addList(items); - - var compare = function(prop) { - return function (a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ]); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - - it('should pass options to array-sort from the sortBy method', function () { - list = new List(); - list.addList(items); - - var compare = function(prop) { - return function (a, b, fn) { - var valA = get(a, prop); - var valB = get(b, prop); - return fn(valA, valB); - }; - }; - - var res = list.sortBy('locals.date', 'doesnt.exist', [ - compare('locals.foo'), - compare('locals.bar') - ], {reverse: true}); - - assert.containEql(res.items, [ - { key: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { key: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { key: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { key: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - { key: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { key: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { key: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { key: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { key: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { key: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { key: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { key: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { key: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } - ]); - }); - }); - - describe('groupBy', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - ]; - - it('should group a list by a property', function () { - list = new List(); - list.addList(items); - - var res = list.groupBy('locals.foo'); - var keys = ['zzz', 'mmm', 'xxx', 'aaa', 'ccc', 'rrr', 'ttt', 'yyy']; - assert.deepEqual(Object.keys(res), keys); - }); - }); - - describe('sort and group', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 5 } }, - { path: 'i.md', locals: { date: '2013-02-01', foo: 'lll', bar: 5 } }, - { path: 'k.md', locals: { date: '2013-03-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2013-02-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'm.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'n.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'o.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'p.md', locals: { date: '2013-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - ]; - - it('should group a list by a property', function () { - list = new List(items); - - var context = list - .sortBy('locals.date') - .groupBy(function (view) { - var date = view.locals.date; - view.locals.year = date.slice(0, 4); - view.locals.month = date.slice(5, 7); - view.locals.day = date.slice(8, 10); - return view.locals.year; - }, 'locals.month'); - - var keys = Object.keys(context); - assert(keys[0] === '2012'); - assert(keys[1] === '2013'); - assert(keys[2] === '2014'); - assert(keys[3] === '2015'); - }); - }); - - describe('paginate', function() { - var items = [ - { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, - { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, - { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, - { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, - { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, - { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, - { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, - { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, - { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, - { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, - { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, - { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, - { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, - ]; - - it('should paginate a list', function () { - list = new List(items); - - var res = list.paginate(); - assert.equal(res.length, 2); - assert.containEql(res[0].items, items.slice(0, 10)); - assert.containEql(res[1].items, items.slice(10)); - }); - - it('should add pager properties', function () { - list = new List({pager: true}); - list.addList(items); - list.items.forEach(function (item, i) { - assert.equal(item.data.pager.index, i); - }); - }); - - it('should paginate a list with given options', function () { - list = new List(items); - var res = list.paginate({limit: 5}); - - assert.equal(res.length, 3); - assert.containEql(res[0].items, items.slice(0, 5)); - assert.containEql(res[1].items, items.slice(5, 10)); - assert.containEql(res[2].items, items.slice(10)); - }); - }); - - describe('Views instance', function() { - beforeEach(function() { - views = new Views(); - }); - - it('should add views from an instance of Views', function () { - views.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - list = new List(views); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.items[1].contents)); - }); - }); - - describe('getIndex', function() { - beforeEach(function() { - list = new List(); - }); - it('should get the index of a key when key is not renamed', function () { - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - }); - - it('should get the index of a key when key is renamed', function () { - list = new List({ - renameKey: function (key) { - return path.basename(key); - } - }); - list.addItem('a/b/c/ddd.hbs', {content: 'ddd'}); - list.addItem('a/b/c/eee.hbs', {content: 'eee'}); - assert(list.getIndex('a/b/c/ddd.hbs') === 0); - assert(list.getIndex('ddd.hbs') === 0); - assert(list.getIndex('a/b/c/eee.hbs') === 1); - assert(list.getIndex('eee.hbs') === 1); - }); - }); - - describe('getItem', function() { - beforeEach(function() { - list = new List(); - }); - - it('should get an view from `views`', function () { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - assert(list.items.length === 2); - assert(isBuffer(list.items[0].contents)); - assert(isBuffer(list.getItem('one').contents)); - assert(list.getItem('one').contents.toString() === 'aaa'); - assert(list.getItem('two').contents.toString() === 'zzz'); - }); - }); - - describe('use', function() { - beforeEach(function() { - list = new List(); - }); - - it('should use middleware on a list', function () { - list.addItem('one', {content: 'aaa'}); - list.addItem('two', {content: 'zzz'}); - - list - .use(function () { - this.set('foo', 'bar'); - }) - .use(function () { - this.set('one', 'two'); - }); - - assert(list.one === 'two'); - assert(list.foo === 'bar'); - }); - }); -}); - diff --git a/test/list.render.js b/test/list.render.js deleted file mode 100644 index b660836..0000000 --- a/test/list.render.js +++ /dev/null @@ -1,137 +0,0 @@ -require('mocha'); -require('should'); -var async = require('async'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var pages; - -describe('render', function () { - describe('rendering', function () { - beforeEach(function () { - pages = new List(); - pages.engine('tmpl', require('engine-base')); - }); - - it('should throw an error when no callback is given:', function () { - (function() { - pages.render({}); - }).should.throw('List#render is async and expects a callback function'); - }); - - it('should throw an error when an engine is not defined:', function (done) { - pages.addItem('foo.bar', {content: '<%= name %>'}); - var page = pages.getItem('foo.bar'); - - pages.render(page, function(err) { - assert(err.message === 'List#render cannot find an engine for: .bar'); - done(); - }); - }); - - it('should use helpers to render a item:', function (done) { - var locals = {name: 'Halle'}; - - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should use helpers when rendering a item:', function (done) { - var locals = {name: 'Halle'}; - pages.helper('upper', function (str) { - return str.toUpperCase(str); - }); - - pages.addItem('a.tmpl', {content: 'a <%= upper(name) %> b', locals: locals}); - var page = pages.getItem('a.tmpl'); - - pages.render(page, function (err, res) { - if (err) return done(err); - assert(res.content === 'a HALLE b'); - done(); - }); - }); - - it('should render a template when contents is a buffer:', function (done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function (err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a template when content is a string:', function (done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - var item = pages.getItem('a.tmpl'); - - pages.render(item, function (err, item) { - if (err) return done(err); - assert(item.contents.toString() === 'b'); - done(); - }); - }); - - it('should render a item from its path:', function (done) { - pages.addItem('a.tmpl', {content: '<%= a %>', locals: {a: 'b'}}); - - pages.render('a.tmpl', function (err, item) { - if (err) return done(err); - assert(item.content === 'b'); - done(); - }); - }); - - it('should use a plugin for rendering:', function (done) { - pages.engine('tmpl', require('engine-base')); - pages.option('engine', 'tmpl'); - - pages.addItems({ - 'a': {content: '<%= title %>', locals: {title: 'aaa'}}, - 'b': {content: '<%= title %>', locals: {title: 'bbb'}}, - 'c': {content: '<%= title %>', locals: {title: 'ccc'}}, - 'd': {content: '<%= title %>', locals: {title: 'ddd'}}, - 'e': {content: '<%= title %>', locals: {title: 'eee'}}, - 'f': {content: '<%= title %>', locals: {title: 'fff'}}, - 'g': {content: '<%= title %>', locals: {title: 'ggg'}}, - 'h': {content: '<%= title %>', locals: {title: 'hhh'}}, - 'i': {content: '<%= title %>', locals: {title: 'iii'}}, - 'j': {content: '<%= title %>', locals: {title: 'jjj'}}, - }); - - pages.use(function (collection) { - collection.option('pager', false); - - collection.renderEach = function (cb) { - var list = new List(collection); - - async.map(list.items, function (item, next) { - collection.render(item, next); - }, cb); - }; - }); - - pages.renderEach(function (err, items) { - if (err) return done(err); - assert(items[0].content === 'aaa'); - assert(items[9].content === 'jjj'); - assert(items.length === 10); - done(); - }); - }); - }); -}); diff --git a/test/list.use.js b/test/list.use.js deleted file mode 100644 index c192a00..0000000 --- a/test/list.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var Item = App.Item; -var list; - -describe('list.use', function () { - beforeEach(function () { - list = new List(); - }); - - it('should expose the instance to `use`:', function (done) { - list.use(function (inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should be chainable:', function (done) { - list.use(function (inst) { - assert(inst instanceof List); - }) - .use(function (inst) { - assert(inst instanceof List); - }) - .use(function (inst) { - assert(inst instanceof List); - done(); - }); - }); - - it('should expose the list to a plugin:', function () { - list.use(function (items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }); - - list.foo('a', {content: '...'}); - assert(list.hasItem('a')); - }); - - it('should expose list when chained:', function () { - list - .use(function (items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should work when a custom `Item` constructor is passed:', function () { - list = new List({Item: require('vinyl')}); - list - .use(function (items) { - assert(items instanceof List); - items.foo = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof List); - items.bar = items.addItem.bind(items); - }) - .use(function (items) { - assert(items instanceof List); - items.baz = items.addItem.bind(items); - }); - - var pages = list; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - }); - - it('should pass to item `use` if a function is returned:', function () { - list.use(function (items) { - assert(items instanceof List); - - return function (item) { - item.foo = items.addItem.bind(items); - assert(item.isItem || item.isView); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); - - it('should be chainable when a item function is returned:', function () { - list - .use(function (items) { - assert(items instanceof List); - - return function (item) { - item.foo = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function (items) { - assert(items instanceof List); - - return function (item) { - item.bar = items.addItem.bind(items); - assert(item instanceof Item); - }; - }) - .use(function (items) { - assert(items instanceof List); - - return function (item) { - item.baz = items.addItem.bind(items); - assert(item instanceof Item); - }; - }); - - list.addItem('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(list.hasItem('a')); - assert(list.hasItem('b')); - assert(list.hasItem('c')); - assert(list.hasItem('d')); - }); -}); diff --git a/test/mergePartials.js b/test/mergePartials.js deleted file mode 100644 index 6b0835f..0000000 --- a/test/mergePartials.js +++ /dev/null @@ -1,108 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('mergePartials', function () { - beforeEach(function () { - app = new App(); - // reset views - app.views = {}; - }); - - it('should merge multiple partials collections onto one collection:', function () { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials(); - actual.should.have.property('partials'); - actual.partials.should.have.properties(['a', 'b', 'c']); - }); - - it('should keep partials collections on separaet collections:', function () { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - }); - - it('should emit `mergePartials`:', function () { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - var arr = []; - - app.on('onMerge', function (view) { - arr.push(view.content); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.not.have.property('partials'); - actual.should.eql({ foos: { a: 'aaa' }, bars: { b: 'bbb' }, bazs: { c: 'ccc' } }); - arr.should.eql(['aaa', 'bbb', 'ccc']); - }); - - it('should handle `onMerge` middleware:', function () { - var opts = { viewType: 'partial' }; - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/./, function (view, next) { - view.content += ' onMerge'; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ - foos: {a: 'aaa onMerge'}, - bars: {b: 'bbb onMerge'}, - bazs: {c: 'ccc onMerge'} - }); - }); - - it('should skip views with `nomerge=true`:', function () { - var opts = { viewType: 'partial' }; - - app.create('foo', opts); - app.create('bar', opts); - app.create('baz', opts); - - app.onMerge(/[ab]/, function (view, next) { - view.options.nomerge = true; - next(); - }); - - app.foo('a', {path: 'a', content: 'aaa'}); - app.bar('b', {path: 'b', content: 'bbb'}); - app.baz('c', {path: 'c', content: 'ccc'}); - - var actual = app.mergePartials({mergePartials: false}); - actual.should.eql({ bazs: { c: 'ccc' } }); - }); -}); - - diff --git a/test/partials.js b/test/partials.js deleted file mode 100644 index 575e7b5..0000000 --- a/test/partials.js +++ /dev/null @@ -1,202 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app, pages; - -describe('partials', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.engine('hbs', require('engine-handlebars')); - - app.create('partials', { viewType: 'partial' }); - app.create('include', { viewType: 'partial' }); - app.create('layouts', { viewType: 'layout' }); - pages = app.create('page'); - }); - - it('should inject a partial with a helper:', function (done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - app.pages('a.tmpl', {path: 'a.tmpl', content: 'a <%= include("base") %> c'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should inject a partial with a helper on a collection:', function (done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{include "base" }} c'}); - var page = pages.getView('a.tmpl'); - - pages.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use handlebars partial with a helper on a collection:', function (done) { - app.include('base', {path: 'base.tmpl', content: 'xyz'}); - pages.engine('.tmpl', require('engine-handlebars')); - pages.helpers(app._.helpers.sync); - pages.asyncHelpers(app._.helpers.async); - pages.addView('a.tmpl', {path: 'a.tmpl', content: 'a {{> base }} c'}); - - var page = pages.getView('a.tmpl'); - var locals = app.mergePartials(this.options); - - pages.render(page, locals, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a xyz c'); - done(); - }); - }); - - it('should use layouts with partials:', function (done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should add `layoutApplied` after layout is applied:', function (done) { - app.layout('default', {path: 'a.tmpl', content: 'a {% body %} c'}); - app.include('b', {path: 'b.tmpl', content: 'b', layout: 'default'}); - app.pages('a.tmpl', {path: 'c.tmpl', content: '<%= include("b") %>'}); - var page = app.pages.getView('a.tmpl'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(app.layouts.getView('default').options.layoutApplied); - assert.equal(view.content, 'a b c'); - done(); - }); - }); - - it('should pass partials to handlebars:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - var page = app.pages.getView('a.hbs'); - - app.render(page, function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should only merge in the specified viewTypes:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - - }); - - it('should merge the specified viewTypes in the order defined:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); - - it('should not merge in partials with `options.nomerge` defined:', function (done) { - app.onMerge(/\.hbs$/, function (view, next) { - app.applyLayout(view); - next(); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default', options: {nomerge: true}}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a foo c'); - done(); - }); - }); - - it('should emit an `onMerge` event:', function (done) { - app.on('onMerge', function (view) { - app.applyLayout(view); - }); - - app.layout('default', {path: 'a.hbs', content: 'a {% body %} c'}); - app.option('mergeTypes', ['includes', 'partials']); - - app.partial('foo', {path: 'bar.hbs', content: 'bar', layout: 'default'}); - app.include('foo', {path: 'foo.hbs', content: 'foo', layout: 'default'}); - - app.pages('a.hbs', {path: 'c.hbs', content: '{{> foo }}'}); - app.pages.getView('a.hbs') - .render(function (err, view) { - if (err) return done(err); - assert.equal(typeof view.content, 'string'); - assert.equal(view.content, 'a bar c'); - done(); - }); - }); -}); diff --git a/test/questions.js b/test/questions.js deleted file mode 100644 index 5ae3bad..0000000 --- a/test/questions.js +++ /dev/null @@ -1,58 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('content', function () { - beforeEach(function () { - app = new App(); - }); - - it('should store a question:', function () { - app.question('a', 'b'); - assert(app.questions); - assert(app.questions.cache); - assert(app.questions.cache.a); - assert(app.questions.cache.a.name === 'a'); - assert(app.questions.cache.a.message === 'b'); - }); - - it('should ask a question and use data value to answer:', function (done) { - app.question('a', 'b'); - app.data('a', 'b'); - - app.ask('a', function (err, answer) { - assert(!err); - assert(answer); - assert(answer === 'b'); - done(); - }) - }); - - it('should ask a question and use store value to answer:', function (done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function (err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); - - it('should ask a question and use config value to answer:', function (done) { - app.question('a', 'b'); - app.store.set('a', 'c'); - - app.ask('a', function (err, answer) { - assert(!err); - assert(answer); - assert(answer === 'c'); - done(); - }) - }); -}); diff --git a/test/renameKey.js b/test/renameKey.js deleted file mode 100644 index 9662f03..0000000 --- a/test/renameKey.js +++ /dev/null @@ -1,350 +0,0 @@ -var path = require('path'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function renameKey(key) { - return path.basename(key, path.extname(key)); -} - -describe('renameKey', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('pages'); - app.create('posts'); - }); - - describe('global options:', function () { - it('should use `renameKey` function defined on global opts:', function () { - app.option('renameKey', renameKey); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('a'); - app.views.posts.should.have.property('b'); - app.views.posts.should.have.property('c'); - app.views.posts.should.have.property('d'); - app.views.posts.should.have.property('e'); - }); - - it('should not have conflicts when view name is the collection name:', function () { - app.option('renameKey', renameKey); - - app.post('a/b/c/post.txt', {content: 'this is contents'}); - app.page('a/b/c/page.txt', {content: 'this is contents'}); - - app.views.posts.should.have.property('post'); - app.views.pages.should.have.property('page'); - }); - }); - - describe('create method:', function () { - it('should use `renameKey` option chained from the `create` method:', function () { - app.create('post') - .option('renameKey', function (key) { - return 'posts/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('create method:', function () { - it('should use `renameKey` defined on the `create` method:', function () { - app.create('post', { - renameKey: function (key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.posts.should.have.property('posts/a.txt'); - app.views.posts.should.have.property('posts/b.txt'); - app.views.posts.should.have.property('posts/c.txt'); - app.views.posts.should.have.property('posts/d.txt'); - app.views.posts.should.have.property('posts/e.txt'); - }); - }); - - describe('collections:', function () { - describe('setting:', function () { - it('should get a view with the `renameKey` defined on app.options:', function () { - app.option('renameKey', function (key) { - return 'foo/' + path.basename(key); - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.views.posts.should.have.property('foo/a.txt'); - app.views.posts.should.have.property('foo/b.txt'); - app.views.posts.should.have.property('foo/c.txt'); - }); - - it('should use `renameKey` defined on collection.options:', function () { - app.pages.option('renameKey', function (key) { - return 'page/' + path.basename(key); - }); - - app.posts.option('renameKey', function (key) { - return 'post/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.posts('a/b/c/c.txt', {content: '...'}); - app.post('a/b/c/d.txt', {content: '...'}); - app.post('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('page/a.txt'); - app.views.pages.should.have.property('page/b.txt'); - app.views.pages.should.have.property('page/c.txt'); - app.views.pages.should.have.property('page/d.txt'); - app.views.pages.should.have.property('page/e.txt'); - - app.views.posts.should.have.property('post/a.txt'); - app.views.posts.should.have.property('post/b.txt'); - app.views.posts.should.have.property('post/c.txt'); - app.views.posts.should.have.property('post/d.txt'); - app.views.posts.should.have.property('post/e.txt'); - }); - - it('should use the `collection.renameKey()` method:', function () { - app.pages.renameKey(function (key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - app.views.pages.should.have.property('baz/d.txt'); - app.views.pages.should.have.property('baz/e.txt'); - }); - - it('should use the `app.renameKey()` method:', function () { - app.renameKey(function (key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('app/a.txt'); - app.views.pages.should.have.property('app/b.txt'); - app.views.pages.should.have.property('app/c.txt'); - app.views.pages.should.have.property('app/d.txt'); - app.views.pages.should.have.property('app/e.txt'); - }); - - it('should prefer collection method over app.options:', function () { - // this works when you switch the order around... - app.pages.renameKey(function pagesRenameKey(key) { - return 'aaa/' + path.basename(key); - }); - app.option('renameKey', function optsRenameKey(key) { - return 'foo/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection method over app method:', function () { - app.pages.renameKey(function (key) { - return 'aaa/' + path.basename(key); - }); - app.renameKey(function (key) { - return 'zzz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('aaa/a.txt'); - app.views.pages.should.have.property('aaa/b.txt'); - app.views.pages.should.have.property('aaa/c.txt'); - app.views.pages.should.have.property('aaa/d.txt'); - app.views.pages.should.have.property('aaa/e.txt'); - }); - - it('should prefer collection options over app.options:', function () { - app.pages.option('renameKey', function (key) { - return 'collection/' + path.basename(key); - }); - app.option('renameKey', function (key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should prefer collection options over app method:', function () { - app.pages.option('renameKey', function (key) { - return 'collection/' + path.basename(key); - }); - app.renameKey(function (key) { - return 'app/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.pages('a/b/c/c.txt', {content: '...'}); - app.page('a/b/c/d.txt', {content: '...'}); - app.page('a/b/c/e.txt', {content: '...'}); - - app.views.pages.should.have.property('collection/a.txt'); - app.views.pages.should.have.property('collection/b.txt'); - app.views.pages.should.have.property('collection/c.txt'); - app.views.pages.should.have.property('collection/d.txt'); - app.views.pages.should.have.property('collection/e.txt'); - }); - - it('should use renameKey on chained methods:', function () { - app.page('test/fixtures/pages/a.txt', { - options: { - renameKey: function foo(key) { - return 'foo/' + path.basename(key); - } - } - }); - - app.page('test/fixtures/pages/a.hbs', { - options: { - renameKey: function bar(key) { - return 'bar/' + path.basename(key); - } - } - }); - - app.views.pages.should.have.properties([ - 'foo/a.txt', - 'bar/a.hbs' - ]); - }); - }); - - describe('getting', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('post'); - app.create('page'); - }); - - it('should get a view with the `renameKey` defined on the `create` method:', function () { - app.create('post', { - renameKey: function createRenameKey(key) { - return 'posts/' + path.basename(key); - } - }); - - app.posts('a/b/c/a.txt', {content: '...'}); - app.posts('a/b/c/b.txt', {content: '...'}); - app.post('a/b/c/c.txt', {content: '...'}); - - app.posts.getView('a.txt').should.have.property('path', 'a/b/c/a.txt'); - app.posts.getView('posts/a.txt').should.have.property('path', 'a/b/c/a.txt'); - }); - - it('should get a view with `renameKey` on collection.options:', function () { - app.pages.option('renameKey', function (key) { - return 'bar/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('bar/a.txt'); - app.views.pages.should.have.property('bar/b.txt'); - app.views.pages.should.have.property('bar/c.txt'); - }); - - it('should get a view with the the `app.renameKey()` method:', function () { - app.renameKey(function (key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - - it('should get a view with the the `collection.renameKey()` method:', function () { - app.pages.renameKey(function (key) { - return 'baz/' + path.basename(key); - }); - - app.pages('a/b/c/a.txt', {content: '...'}); - app.pages('a/b/c/b.txt', {content: '...'}); - app.page('a/b/c/c.txt', {content: '...'}); - - app.views.pages.should.have.property('baz/a.txt'); - app.views.pages.should.have.property('baz/b.txt'); - app.views.pages.should.have.property('baz/c.txt'); - }); - }); - }); -}); diff --git a/test/render.js b/test/render.js deleted file mode 100644 index e097fcd..0000000 --- a/test/render.js +++ /dev/null @@ -1,70 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('render', function () { - describe('engine', function () { - var view; - - beforeEach(function () { - app = new App({silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - view = {contents: new Buffer('a <%= name %> b'), locals: {name: 'Halle'}}; - }); - - it('should render a view from an object:', function (done) { - app.page('a.tmpl', view) - .render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle b'); - done(); - }); - }); - - it('should throw an error when a variable is undefined:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; - - app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); - done(); - }); - }); - - it('should re-throw an error when rethrow is true:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; - - app = new App({rethrow: true, silent: true}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); - done(); - }); - }); - - it('should emit a re-thrown error when rethrow is true:', function (done) { - view = {contents: new Buffer('a <%= foo %> b')}; - - app = new App({rethrow: true, silent: false}); - app.engine('tmpl', require('engine-base')); - app.create('page'); - - app.on('error', function(err) { - assert(err.message === 'foo is not defined'); - done(); - }); - - app.page('a.tmpl', view) - .render(function (err) { - assert(err.message === 'foo is not defined'); - }); - }); - }); -}); diff --git a/test/routes.js b/test/routes.js deleted file mode 100644 index fed137f..0000000 --- a/test/routes.js +++ /dev/null @@ -1,98 +0,0 @@ -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -function append(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(content + ' ' + str); - next(); - }; -} -function prepend(str) { - return function(view, next) { - var content = view.contents.toString(); - view.contents = new Buffer(str + ' ' + content); - next(); - }; -} - -describe('routes', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - describe('params', function () { - it('should call param function when routing', function(done) { - app.param('id', function(view, next, id) { - assert.equal(id, '123'); - next(); - }); - - app.all('/foo/:id/bar', function(view, next) { - assert.equal(view.options.params.id, '123'); - next(); - }); - - app.router.handle({ path: '/foo/123/bar' }, done); - }); - }); - - describe('onLoad middleware', function () { - it('should run when templates are loaded:', function () { - app.onLoad(/\.tmpl/, prepend('onLoad')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>'}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('onLoad <%= name %>'); - }); - }); - - describe('preCompile middleware', function () { - it('should run before templates are compiled:', function () { - - }); - }); - - describe('postCompile middleware', function () { - it('should run after templates are compiled:', function () { - - }); - }); - - describe('preRender middleware', function () { - it('should run before templates are rendered:', function (done) { - app.preRender(/\.tmpl/, prepend('preRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa'} }); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function (err, res) { - if (err) return done(err); - res.contents.toString().should.equal('preRender aaa'); - done(); - }); - }); - }); - - describe('postRender middleware', function () { - it('should run after templates are rendered:', function (done) { - app.postRender(/\.tmpl/, append('postRender')); - app.pages('a.tmpl', { path: 'a.tmpl', content: '<%= name %>', locals: {name: 'aaa' }}); - - var page = app.pages.getView('a.tmpl'); - page.contents.toString().should.equal('<%= name %>'); - - page.render({}, function (err, res) { - if (err) return done(err); - res.contents.toString().should.equal('aaa postRender'); - done(); - }); - }); - }); -}); diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..e680047 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,111 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var argv = require('yargs-parser')(process.argv.slice(2)); +var runner = require('base-runner'); +var Generate = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); +var config = { + name: 'foo', + runner: require(fixtures('package.json')), + configName: 'updater', + extensions: { + '.js': null + } +}; + +describe('.runner', function() { + var error = console.error; + + beforeEach(function() { + console.error = function() {}; + }); + + afterEach(function() { + console.error = error; + }); + + describe('errors', function() { + it('should throw an error when a callback is not passed', function(cb) { + try { + runner(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected a callback function'); + cb(); + } + }); + + it('should error when an options object is not passed', function(cb) { + runner(Generate, {}, null, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the third argument to be an options object'); + cb(); + }); + }); + + it('should error when a liftoff config object is not passed', function(cb) { + runner(Generate, null, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the second argument to be a liftoff config object'); + cb(); + }); + }); + + it('should error when a Generate constructor is not passed', function(cb) { + runner(null, {}, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the first argument to be a Base constructor'); + cb(); + }); + }); + }); + + describe('runner', function() { + it('should set "env" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.env); + assert.equal(typeof app.cache.runnerContext.env, 'object'); + cb(); + }); + }); + + it('should set "config" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.config); + assert.equal(typeof app.cache.runnerContext.config, 'object'); + cb(); + }); + }); + + it('should set the configFile on app.cache.runnerContext.env', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configFile, 'updater.js'); + cb(); + }); + }); + + it('should set cwd on the instance', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cwd, fixtures()); + cb(); + }); + }); + + it('should resolve configpath from app.cwd and app.configFile', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configPath, path.resolve(__dirname, 'fixtures/updater.js')); + cb(); + }); + }); + }); +}); diff --git a/test/store.js b/test/store.js deleted file mode 100644 index de4295f..0000000 --- a/test/store.js +++ /dev/null @@ -1,277 +0,0 @@ -'use strict'; - -require('mocha'); -require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var store = require('base-store'); -var support = require('./support'); -var update = support.resolve(); -var app; - -describe('store', function () { - beforeEach(function () { - app = update(); - app.use(store('update-tests')); - }); - - afterEach(function () { - app.store.data = {}; - app.store.del({force: true}); - }); - - it('should create a store with the given `name`', function () { - app.use(store('foo-bar-baz')); - assert(app.store.name === 'foo-bar-baz'); - }); - - it('should create a store at the given `cwd`', function () { - var cwd = path.resolve(__dirname, 'actual'); - app.use(store('abc', {cwd: cwd})); - app.store.set('foo', 'bar'); - path.basename(app.store.path).should.equal('abc.json'); - app.store.data.should.have.property('foo', 'bar'); - assert.equal(fs.existsSync(path.join(cwd, 'abc.json')), true); - }); - - it('should create a store using the given `indent` value', function () { - var cwd = path.resolve(__dirname, 'actual'); - app.use(store('abc', {cwd: cwd, indent: 0})); - app.store.set('foo', 'bar'); - var contents = fs.readFileSync(path.resolve(cwd, 'abc.json'), 'utf8'); - assert.equal(contents, '{"foo":"bar"}'); - }); - - it('should `.set()` a value on the store', function () { - app.store.set('one', 'two'); - app.store.data.one.should.equal('two'); - }); - - it('should `.set()` an object', function () { - app.store.set({four: 'five', six: 'seven'}); - app.store.data.four.should.equal('five'); - app.store.data.six.should.equal('seven'); - }); - - it('should `.set()` a nested value', function () { - app.store.set('a.b.c.d', {e: 'f'}); - app.store.data.a.b.c.d.e.should.equal('f'); - }); - - it('should `.union()` a value on the store', function () { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - }); - - it('should not union duplicate values', function () { - app.store.union('one', 'two'); - app.store.data.one.should.eql(['two']); - - app.store.union('one', ['two']); - app.store.data.one.should.eql(['two']); - }); - - it('should concat an existing array:', function () { - app.store.union('one', 'a'); - app.store.data.one.should.eql(['a']); - - app.store.union('one', ['b']); - app.store.data.one.should.eql(['a', 'b']); - - app.store.union('one', ['c', 'd']); - app.store.data.one.should.eql(['a', 'b', 'c', 'd']); - }); - - it('should return true if a key `.has()` on the store', function () { - app.store.set('foo', 'bar'); - app.store.set('baz', null); - app.store.set('qux', undefined); - - app.store.has('foo').should.eql(true); - app.store.has('bar').should.eql(false); - app.store.has('baz').should.eql(false); - app.store.has('qux').should.eql(false); - }); - - it('should return true if a nested key `.has()` on the store', function () { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - app.store.has('a.b.bar').should.eql(false); - app.store.has('a.b.c.d').should.eql(true); - app.store.has('a.b.c.d.x').should.eql(true); - app.store.has('a.b.c.d.z').should.eql(false); - app.store.has('a.b.c.e').should.eql(true); - app.store.has('a.b.c.e.f').should.eql(false); - app.store.has('a.b.c.e.z').should.eql(false); - app.store.has('a.b.g.j').should.eql(true); - app.store.has('a.b.g.j.k').should.eql(false); - app.store.has('a.b.g.j.z').should.eql(false); - }); - - it('should return true if a key exists `.hasOwn()` on the store', function () { - app.store.set('foo', 'bar'); - app.store.set('baz', null); - app.store.set('qux', undefined); - - app.store.hasOwn('foo').should.eql(true); - app.store.hasOwn('bar').should.eql(false); - app.store.hasOwn('baz').should.eql(true); - app.store.hasOwn('qux').should.eql(true); - }); - - it('should return true if a nested key exists `.hasOwn()` on the store', function () { - app.store.set('a.b.c.d', {x: 'zzz'}); - app.store.set('a.b.c.e', {f: null}); - app.store.set('a.b.g.j', {k: undefined}); - - app.store.hasOwn('a.b.bar').should.eql(false); - app.store.hasOwn('a.b.c.d').should.eql(true); - app.store.hasOwn('a.b.c.d.x').should.eql(true); - app.store.hasOwn('a.b.c.d.z').should.eql(false); - app.store.has('a.b.c.e.f').should.eql(false); - app.store.hasOwn('a.b.c.e.f').should.eql(true); - app.store.hasOwn('a.b.c.e.bar').should.eql(false); - app.store.has('a.b.g.j.k').should.eql(false); - app.store.hasOwn('a.b.g.j.k').should.eql(true); - app.store.hasOwn('a.b.g.j.foo').should.eql(false); - }); - - it('should `.get()` a stored value', function () { - app.store.set('three', 'four'); - app.store.get('three').should.equal('four'); - }); - - it('should `.get()` a nested value', function () { - app.store.set({a: {b: {c: 'd'}}}); - app.store.get('a.b.c').should.equal('d'); - }); - - it('should `.del()` a stored value', function () { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.data.should.have.property('a'); - app.store.data.should.have.property('c'); - - app.store.del('a'); - app.store.del('c'); - app.store.data.should.not.have.property('a'); - app.store.data.should.not.have.property('c'); - }); - - it('should `.del()` multiple stored values', function () { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - app.store.del(['a', 'c', 'e']); - app.store.data.should.eql({}); - }); -}); - -describe('events', function () { - beforeEach(function () { - app.use(store('abc')); - }); - - afterEach(function () { - app.store.data = {}; - app.store.del({force: true}); - }); - - it('should emit `set` when an object is set:', function () { - var keys = []; - app.store.on('set', function (key) { - keys.push(key); - }); - - app.store.set({a: {b: {c: 'd'}}}); - keys.should.eql(['a']); - }); - - it('should emit `set` when a key/value pair is set:', function () { - var keys = []; - - app.store.on('set', function (key) { - keys.push(key); - }); - - app.store.set('a', 'b'); - keys.should.eql(['a']); - }); - - it('should emit `set` when an object value is set:', function () { - var keys = []; - - app.store.on('set', function (key) { - keys.push(key); - }); - - app.store.set('a', {b: 'c'}); - keys.should.eql(['a']); - }); - - it('should emit `set` when an array of objects is passed:', function (cb) { - var keys = []; - - app.store.on('set', function (key) { - keys.push(key); - }); - - app.store.set([{a: 'b'}, {c: 'd'}]); - keys.should.eql(['a', 'c']); - cb(); - }); - - it('should emit `has`:', function (cb) { - var keys = []; - - app.store.on('has', function (val) { - assert(val); - cb(); - }); - - app.store.set('a', 'b'); - app.store.has('a'); - }); - - it('should emit `del` when a value is delted:', function (cb) { - app.store.on('del', function (keys) { - keys.should.eql('a'); - assert(typeof app.store.get('a') === 'undefined'); - cb(); - }); - - app.store.set('a', {b: 'c'}); - app.store.get('a').should.eql({b: 'c'}); - app.store.del('a'); - }); - - it('should emit deleted keys on `del`:', function (cb) { - var arr = []; - - app.store.on('del', function (key) { - arr.push(key); - assert(Object.keys(app.store.data).length === 0); - }); - - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - - app.store.del({force: true}); - arr.should.eql(['a', 'c', 'e']); - cb(); - }); - - it('should throw an error if force is not passed', function () { - app.store.set('a', 'b'); - app.store.set('c', 'd'); - app.store.set('e', 'f'); - - (function () { - app.store.del(); - }).should.throw('options.force is required to delete the entire cache.'); - }); -}); diff --git a/test/support/ignore.js b/test/support/ignore.js index ef59903..7dcbb75 100644 --- a/test/support/ignore.js +++ b/test/support/ignore.js @@ -3,4 +3,4 @@ module.exports = [ 'removeEventListener', 'removeAllListeners', 'removeListener' -]; \ No newline at end of file +]; diff --git a/test/support/index.js b/test/support/index.js index 899c846..e2194a5 100644 --- a/test/support/index.js +++ b/test/support/index.js @@ -56,9 +56,9 @@ exports.resolve = function(filepath) { function tryResolve(name) { try { return require.resolve(name); - } catch(err) {} + } catch (err) {} try { return require.resolve(path.resolve(name)); - } catch(err) {} + } catch (err) {} } diff --git a/test/support/spy.js b/test/support/spy.js index e14512b..b473c6e 100644 --- a/test/support/spy.js +++ b/test/support/spy.js @@ -1,4 +1,6 @@ -var fs = require('fs'); +'use strict'; + +var fs = require('graceful-fs'); var sinon = require('sinon'); var errorfn = false; @@ -8,8 +10,7 @@ function maybeCallAsync(module, func) { return sinon.stub(module, func, function() { var args = Array.prototype.slice.call(arguments); args.unshift(module, func); - var err = typeof errorfn === 'function' && - errorfn.apply(this, args); + var err = typeof errorfn === 'function' && errorfn.apply(this, args); if (!err) { original.apply(this, arguments); } else { @@ -23,5 +24,8 @@ module.exports = { errorfn = fn; }, chmodSpy: maybeCallAsync(fs, 'chmod'), - statSpy: maybeCallAsync(fs, 'stat') + fchmodSpy: maybeCallAsync(fs, 'fchmod'), + futimesSpy: maybeCallAsync(fs, 'futimes'), + statSpy: maybeCallAsync(fs, 'stat'), + fstatSpy: maybeCallAsync(fs, 'fstat') }; diff --git a/test/update.js b/test/update.js new file mode 100644 index 0000000..17378a4 --- /dev/null +++ b/test/update.js @@ -0,0 +1,35 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Update = require('..'); +var update; + +describe('update', function() { + describe('cwd', function() { + beforeEach(function() { + update = new Update(); + }); + + it('should get the current working directory', function() { + assert.equal(update.cwd, process.cwd()); + }); + + it('should set the current working directory', function() { + update.cwd = 'test/fixtures'; + assert.equal(update.cwd, path.join(process.cwd(), 'test/fixtures')); + }); + }); + + describe('generator', function() { + beforeEach(function() { + update = new Update(); + }); + + it('should register the default generator', function() { + update.register('default', require('./fixtures/def-gen')); + assert(update.getGenerator('default')); + }); + }); +}); diff --git a/test/updaters.env.js b/test/updaters.env.js new file mode 100644 index 0000000..f2d2e97 --- /dev/null +++ b/test/updaters.env.js @@ -0,0 +1,127 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; +var env; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('env', function() { + describe('plugin', function() { + it('should work as a plugin', function() { + base = new Base(); + assert.equal(typeof base.createEnv, 'function'); + }); + }); + + describe('createEnv paths', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('alias and function', function() { + it('should make the alias the exact name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.alias); + assert.equal(env.alias, 'foo-bar-baz'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + + describe('alias and path', function() { + it('should set the env.name using the given name', function() { + var env = base.createEnv('foo', fixtures('updater-foo/updatefile.js')); + assert.equal(env.name, 'foo'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + }); + + describe('createEnv', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should add an env object to the instance', function() { + var fn = function() {}; + env = base.createEnv('foo', fn); + assert(env); + }); + + it('should take options as the second arg', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should prime `env` if it doesn\'t exist', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should add an alias to the env object', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert.equal(env.alias, 'foo'); + }); + + it('should not prefix the alias when a function is passed', function() { + var fn = function() {}; + delete base.prefix; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should not prefix a custom alias when a function is passed', function() { + var fn = function() {}; + base.prefix = 'whatever'; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve an absolute path passed as the second arg', function() { + env = base.createEnv('foo', fixtures('updater.js')); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve a relative path passed as the second arg', function() { + env = base.createEnv('foo', fixtures('updater-foo/updatefile.js')); + assert.equal(env.key, 'foo'); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should throw an error when the path is not resolved', function(cb) { + try { + var env = base.createEnv('foo', fixtures('whatever.js')); + env.invoke(); + env.path; + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot resolve: \'' + fixtures('whatever.js') + '\''); + cb(); + } + }); + }); +}); diff --git a/test/updaters.events.js b/test/updaters.events.js new file mode 100644 index 0000000..00db2ae --- /dev/null +++ b/test/updaters.events.js @@ -0,0 +1,157 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var updaters = require('..'); + +describe('updaters events', function() { + describe('generator', function() { + beforeEach(function() { + base = new Base(); + base.enable('silent'); + }); + + it('should emit generator when a generator is registered', function(cb) { + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + }); + + it('should emit generator when base.updaters.get is called', function(cb) { + + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit generator.get when base.updaters.get is called', function(cb) { + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit error on base when a base generator emits an error', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.emit('error', new Error('whatever')); + }); + + base.getGenerator('foo'); + assert.equal(called, 1); + cb(); + }); + + it('should emit error on base when a base generator throws an error', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + + base.getGenerator('foo') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should emit errors on base from deeply nested updaters', function(cb) { + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.register('b', function() { + this.register('c', function() { + this.register('d', function() { + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should bubble up errors to all parent updaters', function(cb) { + var called = 0; + + function count() { + called++; + } + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.on('error', count); + + this.register('b', function() { + this.on('error', count); + + this.register('c', function() { + this.on('error', count); + + this.register('d', function() { + this.on('error', count); + + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 6); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + }); +}); diff --git a/test/view.content.js b/test/view.content.js deleted file mode 100644 index ccd21f7..0000000 --- a/test/view.content.js +++ /dev/null @@ -1,29 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('content', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - }); - - it('should normalize the `content` property on a view to a string:', function (done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function () { - this.contents = fs.readFileSync(this.path); - return this; - }); - - app.views.pages.abc.read(); - - assert('content' in app.views.pages.abc); - assert(typeof app.views.pages.abc.content === 'string'); - done(); - }); -}); diff --git a/test/view.events.js b/test/view.events.js deleted file mode 100644 index eef1dcd..0000000 --- a/test/view.events.js +++ /dev/null @@ -1,28 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should emit events:', function () { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - var events = []; - - page.on('option', function (key) { - events.push(key); - }); - - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.option({g: 'h'}); - - events.should.eql(['a', 'c', 'e', 'g']); - }); -}); diff --git a/test/view.js b/test/view.js deleted file mode 100644 index c83b651..0000000 --- a/test/view.js +++ /dev/null @@ -1,1153 +0,0 @@ -require('mocha'); -var should = require('should'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var Stream = require('stream'); -var es = require('event-stream'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('View', function () { - describe('instance', function () { - it('should create an instance of View:', function () { - view = new View(); - assert(view instanceof View); - }); - }); - - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof View.extend === 'function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function () { - view = new View(); - }); - - it('should expose `set`:', function () { - assert(typeof view.set === 'function'); - }); - it('should expose `get`:', function () { - assert(typeof view.get === 'function'); - }); - it('should expose `del`:', function () { - assert(typeof view.del === 'function'); - }); - it('should expose `define`:', function () { - assert(typeof view.define === 'function'); - }); - it('should expose `visit`:', function () { - assert(typeof view.visit === 'function'); - }); - it('should expose `compile`:', function () { - assert(typeof view.compile === 'function'); - }); - it('should expose `render`:', function () { - assert(typeof view.render === 'function'); - }); - }); - - describe('properties', function () { - it('should expose an `options` property', function () { - view = new View({}); - assert.deepEqual(view.options, {}); - assert(view.hasOwnProperty('options')); - }); - - it('should add `options` when passed on the constructor', function () { - view = new View({options: {foo: 'bar'}}); - assert(view.options.foo === 'bar'); - }); - - it('should expose a `data` property', function () { - view = new View({app: {}}); - assert.deepEqual(view.data, {}); - assert(view.hasOwnProperty('data')); - }); - - it('should add `data` when passed on the constructor', function () { - view = new View({data: {foo: 'bar'}}); - assert(view.data.foo === 'bar'); - }); - - it('should add `locals` when passed on the constructor', function () { - view = new View({locals: {foo: 'bar'}}); - assert(view.locals.foo === 'bar'); - }); - }); - - describe('set', function () { - it('should set properties on the object', function () { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.foo, 'bar'); - }); - }); - - describe('get', function () { - it('should get properties from the object', function () { - view = new View(); - view.set('foo', 'bar'); - assert.equal(view.get('foo'), 'bar'); - }); - }); - - describe('cwd', function () { - it('should get properties from the object', function () { - view = new View({cwd: 'test/fixtures'}); - assert(view.cwd === 'test/fixtures'); - }); - }); - - describe('clone', function () { - it('should clone the view:', function () { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone(); - assert(clone.contents); - clone.set('baz', 'quux'); - clone.set('options.three', 'four'); - assert.equal(clone.get('foo'), view.get('foo')); - assert(clone.get('baz') === 'quux'); - assert(!view.get('baz')); - // not deep cloned - assert(clone.get('options.three') === 'four'); - assert(view.get('options.three') === 'four'); - }); - - it('should deep clone the entire object', function () { - view = new View({content: 'foo'}); - view.set({path: 'foo/bar'}); - view.set('options.one', 'two'); - var clone = view.clone({deep: true}); - clone.set('options.three', 'four'); - assert(view.get('options.one') === 'two'); - assert(clone.get('options.one') === 'two'); - assert(clone.get('options.three') === 'four'); - assert(!view.get('options.three')); - }); - }); - - describe('visit', function () { - it('should visit all properties on an object and call the specified method', function () { - view = new View(); - var obj = { - foo: 'bar', - bar: 'baz', - baz: 'bang' - }; - view.visit('set', obj); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - - it('should visit all properties on all objects in an array and call the specified method', function () { - view = new View(); - var arr = [{foo: 'bar', bar: 'baz', baz: 'bang'}]; - view.visit('set', arr); - assert.equal(view.get('foo'), 'bar'); - assert.equal(view.get('bar'), 'baz'); - assert.equal(view.get('baz'), 'bang'); - }); - }); - - describe('compile', function () { - it('should get view.layout from view.data.layout', function () { - view = new View({path: 'foo', contents: 'a b c', data: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.options.layout', function () { - view = new View({path: 'foo', contents: 'a b c', options: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from view.locals.layout', function () { - view = new View({path: 'foo', contents: 'a b c', locals: {layout: 'default'}}); - assert(view.layout === 'default'); - }); - it('should get view.layout from the view', function () { - view = new View({path: 'foo', contents: 'a b c', layout: 'default'}); - assert(view.layout === 'default'); - }); - - it('should add a compiled function to `view.fn`', function () { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - assert(typeof view.fn === 'function'); - }); - - it('should render a compiled template', function (done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.compile(); - view.render({name: 'Halle'}, function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render `fn` using data passed on the constructor', function (done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.compile(); - view.render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - }); - - describe('render', function () { - it('should render a template', function (done) { - view = new View({path: 'foo', contents: 'a <%= name %> z'}); - view.render({name: 'Halle'}, function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Halle z'); - done(); - }); - }); - - it('should render fn using data passed on the constructor', function (done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z', - data: { - name: 'Brooke' - } - }); - - view.render(function (err, res) { - if (err) return done(err); - assert(res.contents.toString() === 'a Brooke z'); - done(); - }); - }); - - it('should pass errors in the callback.', function (done) { - view = new View({ - path: 'foo', - contents: 'a <%= name %> z' - }); - - view.render(function (err) { - assert(err.message === 'name is not defined'); - done(); - }); - }); - }); -}); - -/** - * The following unit tests are from Vinyl - * Since we inherit vinyl in View, we need - * to ensure that these still pass. - */ - -describe('View', function() { - describe('isVinyl()', function() { - it('should return true on a vinyl object', function(done) { - var view = new View(); - assert(View.isVinyl(view) === true); - done(); - }); - it('should return false on a normal object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - it('should return false on a null object', function(done) { - assert(View.isVinyl({}) === false); - done(); - }); - }); - - describe('constructor()', function() { - it('should default cwd to process.cwd', function(done) { - var view = new View(); - view.cwd.should.equal(process.cwd()); - done(); - }); - - it('should default base to cwd', function(done) { - var cwd = '/'; - var view = new View({cwd: cwd}); - view.base.should.equal(cwd); - done(); - }); - - it('should default base to cwd even when none is given', function(done) { - var view = new View(); - view.base.should.equal(process.cwd()); - done(); - }); - - it('should default path to null', function(done) { - var view = new View(); - should.not.exist(view.path); - done(); - }); - - it('should default history to []', function(done) { - var view = new View(); - view.history.should.eql([]); - done(); - }); - - it('should default stat to null', function(done) { - var view = new View(); - should.not.exist(view.stat); - done(); - }); - - it('should default contents to null', function(done) { - var view = new View(); - should.not.exist(view.contents); - done(); - }); - - it('should set base to given value', function(done) { - var val = '/'; - var view = new View({base: val}); - view.base.should.equal(val); - done(); - }); - - it('should set cwd to given value', function(done) { - var val = '/'; - var view = new View({cwd: val}); - view.cwd.should.equal(val); - done(); - }); - - it('should set path to given value', function(done) { - var val = '/test.coffee'; - var view = new View({path: val}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set history to given value', function(done) { - var val = '/test.coffee'; - var view = new View({history: [val]}); - view.path.should.equal(val); - view.history.should.eql([val]); - done(); - }); - - it('should set stat to given value', function(done) { - var val = {}; - var view = new View({stat: val}); - view.stat.should.equal(val); - done(); - }); - - it('should set contents to given value', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.contents.should.equal(val); - done(); - }); - }); - - describe('isBuffer()', function() { - it('should return true when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.isBuffer().should.equal(true); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - view.isBuffer().should.equal(false); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - view.isBuffer().should.equal(false); - done(); - }); - }); - - describe('isStream()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.isStream().should.equal(false); - done(); - }); - - it('should return true when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - view.isStream().should.equal(true); - done(); - }); - - it('should return false when the contents are a null', function(done) { - var view = new View({contents: null}); - view.isStream().should.equal(false); - done(); - }); - }); - - describe('isNull()', function() { - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val}); - view.isNull().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val}); - view.isNull().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null}); - view.isNull().should.equal(true); - done(); - }); - }); - - describe('isDirectory()', function() { - var fakeStat = { - isDirectory: function() { - return true; - } - }; - - it('should return false when the contents are a Buffer', function(done) { - var val = new Buffer('test'); - var view = new View({contents: val, stat: fakeStat}); - view.isDirectory().should.equal(false); - done(); - }); - - it('should return false when the contents are a Stream', function(done) { - var val = new Stream(); - var view = new View({contents: val, stat: fakeStat}); - view.isDirectory().should.equal(false); - done(); - }); - - it('should return true when the contents are a null', function(done) { - var view = new View({contents: null, stat: fakeStat}); - view.isDirectory().should.equal(true); - done(); - }); - }); - - describe('clone()', function() { - it('should copy all attributes over with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'buffer ref should be different'); - view2.contents.toString('utf8').should.equal(view.contents.toString('utf8')); - done(); - }); - - it('should copy buffer\'s reference with option contents: false', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test') - }; - - var view = new View(options); - - var copy1 = view.clone({ contents: false }); - copy1.contents.should.equal(view.contents); - - var copy2 = view.clone({}); - copy2.contents.should.not.equal(view.contents); - - var copy3 = view.clone({ contents: 'any string' }); - copy3.contents.should.not.equal(view.contents); - - done(); - }); - - it('should copy all attributes over with Stream', function(done) { - var contents = new Stream.PassThrough(); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: contents - }; - var view = new View(options); - var view2 = view.clone(); - - contents.write(new Buffer('wa')); - - process.nextTick(function() { - contents.write(new Buffer('dup')); - contents.end(); - }); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.contents.should.not.equal(view.contents, 'stream ref should not be the same'); - view.contents.pipe(es.wait(function(err, data) { - view2.contents.pipe(es.wait(function(err, data2) { - data2.should.not.equal(data, 'stream contents ref should not be the same'); - data2.should.eql(data, 'stream contents should be the same'); - })); - })); - done(); - }); - - it('should copy all attributes over with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - should.not.exist(view2.contents); - done(); - }); - - it('should properly clone the `stat` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - assert(copy.stat.isFile()); - assert(!copy.stat.isDirectory()); - - assert(view.stat.hasOwnProperty('birthtime')); - assert(copy.stat.hasOwnProperty('birthtime')); - assert.deepEqual(view.stat, copy.stat); - done(); - }); - - it('should properly clone the `history` property', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.js', - contents: new Buffer('test'), - stat: fs.statSync(__filename) - }; - - var view = new View(options); - var copy = view.clone(); - - copy.history[0].should.equal(options.path); - copy.path = 'lol'; - view.path.should.not.equal(copy.path); - done(); - }); - - it('should copy custom properties', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - var view2 = view.clone(); - - view2.should.not.equal(view, 'refs should be different'); - view2.cwd.should.equal(view.cwd); - view2.base.should.equal(view.base); - view2.path.should.equal(view.path); - view2.custom.should.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - done(); - }); - - it('should copy history', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.path = '/test/test.js'; - view.path = '/test/test-938di2s.js'; - var view2 = view.clone(); - - view2.history.should.eql([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.history.should.not.equal([ - '/test/test.coffee', - '/test/test.js', - '/test/test-938di2s.js' - ]); - view2.path.should.eql('/test/test-938di2s.js'); - - done(); - }); - - it('should copy all attributes deeply', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - - var view = new View(options); - view.custom = { a: 'custom property' }; - - var view2 = view.clone(true); - view2.custom.should.eql(view.custom); - view2.custom.should.not.equal(view.custom); - view2.custom.a.should.equal(view.custom.a); - - var view3 = view.clone({ deep: true }); - view3.custom.should.eql(view.custom); - view3.custom.should.not.equal(view.custom); - view3.custom.a.should.equal(view.custom.a); - - var view4 = view.clone(false); - view4.custom.should.eql(view.custom); - view4.custom.should.equal(view.custom); - view4.custom.a.should.equal(view.custom.a); - - var view5 = view.clone({ deep: false }); - view5.custom.should.eql(view.custom); - view5.custom.should.equal(view.custom); - view5.custom.a.should.equal(view.custom.a); - - done(); - }); - }); - - describe('pipe()', function() { - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - done(); - }); - var ret = view.pipe(stream); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should write to stream with Buffer', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Buffer('test') - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(options.contents.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - }); - - it('should pipe to stream with Stream', function(done) { - var testChunk = new Buffer('test'); - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function(chunk) { - should.exist(chunk); - (chunk instanceof Buffer).should.equal(true, 'should write as a buffer'); - chunk.toString('utf8').should.equal(testChunk.toString('utf8')); - done(); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - - view.contents.write(testChunk); - }); - - it('should do nothing with null', function(done) { - var options = { - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }; - var view = new View(options); - var stream = new Stream.PassThrough(); - stream.on('data', function() { - throw new Error('should not write'); - }); - stream.on('end', function() { - throw new Error('should not end'); - }); - var ret = view.pipe(stream, {end: false}); - ret.should.equal(stream, 'should return the stream'); - process.nextTick(done); - }); - }); - - describe('inspect()', function() { - it('should return correct format when no contents and no path', function(done) { - var view = new View(); - view.inspect().should.equal(''); - done(); - }); - - it('should return correct format when Buffer and no path', function(done) { - var val = new Buffer('test'); - var view = new View({ - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and relative path', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: val - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Buffer and only path and no base', function(done) { - var val = new Buffer('test'); - var view = new View({ - cwd: '/', - path: '/test/test.coffee', - contents: val - }); - delete view.base; - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when Stream and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: new Stream.PassThrough() - }); - view.inspect().should.equal('>'); - done(); - }); - - it('should return correct format when null and relative path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee', - contents: null - }); - view.inspect().should.equal(''); - done(); - }); - }); - - describe('contents get/set', function() { - it('should work with Buffer', function(done) { - var val = new Buffer('test'); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with Stream', function(done) { - var val = new Stream.PassThrough(); - var view = new View(); - view.contents = val; - view.contents.should.equal(val); - done(); - }); - - it('should work with null', function(done) { - var val = null; - var view = new View(); - view.contents = val; - (view.contents === null).should.equal(true); - done(); - }); - - it('should work with string', function(done) { - var val = 'test'; - var view = new View(); - view.contents = val; - view.contents.should.deepEqual(new Buffer(val)); - done(); - }); - }); - - describe('relative get/set', function() { - it('should error on set', function(done) { - var view = new View(); - try { - view.relative = 'test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no base', function(done) { - var a; - var view = new View(); - delete view.base; - try { - a = view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should error on get when no path', function(done) { - var a; - var view = new View(); - try { - a = view.relative; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return a relative path from base', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.relative.should.equal('test.coffee'); - done(); - }); - - it('should return a relative path from cwd', function(done) { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - view.relative.should.equal(path.join('test','test.coffee')); - done(); - }); - }); - - describe('dirname get/set', function() { - it('should error on get when no path', function(done) { - var a; - var view = new View(); - try { - a = view.dirname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname.should.equal('/test'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.dirname = '/test'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the dirname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.dirname = '/test/foo'; - view.path.should.equal('/test/foo/test.coffee'); - done(); - }); - }); - - describe('basename get/set', function() { - it('should error on get when no path', function(done) { - var a; - var view = new View(); - try { - a = view.basename; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename.should.equal('test.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.basename = 'test.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the basename of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.basename = 'foo.png'; - view.path.should.equal('/test/foo.png'); - done(); - }); - }); - - describe('extname get/set', function() { - it('should error on get when no path', function(done) { - var a; - var view = new View(); - try { - a = view.extname; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should return the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname.should.equal('.coffee'); - done(); - }); - - it('should error on set when no path', function(done) { - var view = new View(); - try { - view.extname = '.coffee'; - } catch (err) { - should.exist(err); - done(); - } - }); - - it('should set the extname of the path', function(done) { - var view = new View({ - cwd: '/', - base: '/test/', - path: '/test/test.coffee' - }); - view.extname = '.png'; - view.path.should.equal('/test/test.png'); - done(); - }); - }); - - describe('path get/set', function() { - - it('should record history when instantiation', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should record history when path change', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.js'; - view.path.should.eql('/test/test.js'); - view.history.should.eql(['/test/test.coffee', '/test/test.js']); - - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee', '/test/test.js', '/test/test.coffee']); - }); - - it('should not record history when set the same path', function() { - var view = new View({ - cwd: '/', - path: '/test/test.coffee' - }); - - view.path = '/test/test.coffee'; - view.path = '/test/test.coffee'; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - - // ignore when set empty string - view.path = ''; - view.path.should.eql('/test/test.coffee'); - view.history.should.eql(['/test/test.coffee']); - }); - - it('should throw when set path null in constructor', function() { - (function() { - new View({ - cwd: '/', - path: null - }); - }).should.throw('path should be string'); - }); - - it('should throw when set path null', function() { - var view = new View({ - cwd: '/', - path: 'foo' - }); - - (function() { - view.path = null; - }).should.throw('path should be string'); - }); - }); -}); diff --git a/test/view.methods.js b/test/view.methods.js deleted file mode 100644 index bb120c6..0000000 --- a/test/view.methods.js +++ /dev/null @@ -1,40 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - app.create('page'); - }); - - - describe('.use', function () { - it('should expose `.use` for running plugins on a view:', function () { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .use(function () { - this.options.foo = 'bar'; - }) - .use(function () { - this.options.bar = 'baz'; - }); - - var page = app.pages.getView('a.tmpl'); - page.options.should.have.property('foo'); - page.options.should.have.property('bar'); - }); - }); - - describe('.render:', function () { - it('should expose `.render` for rendering a view:', function (done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>', locals: {a: 'bbb'}}) - .render({}, function (err, res) { - if (err) return done(err); - res.contents.toString().should.equal('bbb'); - done(); - }); - }); - }); -}); diff --git a/test/view.option.js b/test/view.option.js deleted file mode 100644 index 4190be7..0000000 --- a/test/view.option.js +++ /dev/null @@ -1,29 +0,0 @@ -require('should'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('view.option()', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - }); - - it('should set an option:', function () { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - - page.options.should.not.have.property('foo'); - page.option('foo', 'bar'); - page.options.should.have.property('foo'); - }); - - it('should extend options:', function () { - app.pages('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}); - var page = app.pages.getView('a.tmpl'); - page.option('a', 'b'); - page.option('c', 'd'); - page.option('e', 'f'); - page.options.should.have.properties(['a', 'c', 'e']); - }); -}); diff --git a/test/view.render.js b/test/view.render.js deleted file mode 100644 index 5fe5c7d..0000000 --- a/test/view.render.js +++ /dev/null @@ -1,54 +0,0 @@ -require('mocha'); -require('should'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view, app; - -describe('helpers', function () { - describe('rendering', function () { - beforeEach(function () { - app = new App(); - view = new View(); - app.engine('tmpl', require('engine-base')); - app.create('layouts', {viewType: 'layout'}); - app.create('pages'); - }); - - it('should expose `.render` for rendering a view:', function (done) { - app.page('a.tmpl', {path: 'a.tmpl', content: '<%= a %>'}) - .render({a: 'bbb'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('bbb'); - done(); - }); - }); - - it('should render a view with a layout', function (done) { - app.layout('default.tmpl', {content: 'a {% body %} b'}); - app.page('a.tmpl', {content: '<%= title %>', layout: 'default.tmpl'}) - .render({title: 'zzz'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('a zzz b'); - done(); - }); - }); - - it('should render a view with a layout', function (done) { - app.layout('foo.tmpl', {content: 'a {% body %} a'}); - app.layout('bar.tmpl', {content: 'b {% body %} b'}); - app.pages('a.tmpl', {content: '<%= title %>'}); - - app.pages.getView('a.tmpl') - .option('resolveLayout', function () { - return 'bar.tmpl'; - }) - .render({title: 'zzz'}, function (err, res) { - if (err) return done(err); - res.content.should.equal('b zzz b'); - done(); - }); - }); - }); -}); - diff --git a/test/view.set.js b/test/view.set.js deleted file mode 100644 index 180bcdc..0000000 --- a/test/view.set.js +++ /dev/null @@ -1,35 +0,0 @@ -require('mocha'); -require('should'); -var fs = require('fs'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('set', function () { - beforeEach(function () { - app = new App(); - app.create('page'); - app.engine('tmpl', require('engine-base')); - - app.cache.data = {}; - }); - - it('should set a property on a view:', function (done) { - app.page('abc', {path: 'test/fixtures/templates/a.tmpl'}) - .set('read', function () { - this.contents = fs.readFileSync(this.path); - return this; - }); - - assert('read' in app.views.pages.abc); - app.views.pages.abc - .read() - .set('data.name', 'Brooke') - .render(function (err, res) { - if (err) return done(err); - assert(res.content === 'Brooke'); - done(); - }); - }); -}); diff --git a/test/view.use.js b/test/view.use.js deleted file mode 100644 index 3706c0b..0000000 --- a/test/view.use.js +++ /dev/null @@ -1,60 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var View = App.View; -var view; - -describe('view.use', function () { - beforeEach(function () { - view = new View(); - }); - - it('should expose the instance to `use`:', function (done) { - view.use(function (inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should be chainable:', function (done) { - view.use(function (inst) { - assert(inst instanceof View); - }) - .use(function (inst) { - assert(inst instanceof View); - }) - .use(function (inst) { - assert(inst instanceof View); - done(); - }); - }); - - it('should expose the view to a plugin:', function () { - view.use(function (view) { - assert(view instanceof View); - view.foo = function (str) { - return str + ' ' + 'bar'; - }; - }); - assert(view.foo('foo') === 'foo bar'); - }); - - it('should be chainable:', function () { - view - .use(function (view) { - view.a = 'aaa'; - }) - .use(function (view) { - view.b = 'bbb'; - }) - .use(function (view) { - view.c = 'ccc'; - }); - - assert(view.a === 'aaa'); - assert(view.b === 'bbb'); - assert(view.c === 'ccc'); - }); -}); diff --git a/test/viewTypes.js b/test/viewTypes.js deleted file mode 100644 index 47da0a9..0000000 --- a/test/viewTypes.js +++ /dev/null @@ -1,33 +0,0 @@ -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var app; - -describe('viewType', function () { - describe('view types', function () { - beforeEach(function () { - app = new App(); - app.engine('tmpl', require('engine-base')); - }); - - it('should add collection (plural) to the `viewTypes` object', function () { - app.viewTypes = []; // reset - app.create('foo', {viewType: 'layout'}); - app.create('bar', {viewType: 'layout'}); - assert.deepEqual(app.viewTypes.layout, [ 'foos', 'bars' ]); - - app.create('baz', {viewType: 'renderable'}); - assert.deepEqual(app.viewTypes.renderable, [ 'bazs' ]); - }); - - it('should add collection to the given viewType', function () { - app.create('layout', {viewType: 'layout'}); - assert(app.layouts.options.viewType[0] === 'layout'); - }); - - it('should add a collection to multiple viewTypes', function () { - app.create('foo', {viewType: ['layout', 'renderable']}); - assert.deepEqual(app.foos.options.viewType, ['layout', 'renderable']); - }); - }); -}); diff --git a/test/views.js b/test/views.js deleted file mode 100644 index 8f99b7d..0000000 --- a/test/views.js +++ /dev/null @@ -1,505 +0,0 @@ -require('mocha'); -require('should'); -var path = require('path'); -var assert = require('assert'); -var typeOf = require('kind-of'); -var isBuffer = require('is-buffer'); -var support = require('./support'); -var App = support.resolve(); -var List = App.List; -var View = App.View; -var Views = App.Views; -var collection; - -describe('views', function () { - describe('constructor', function () { - it('should create an instance of Views:', function () { - var collection = new Views(); - assert(collection instanceof Views); - }); - - it('should instantiate without `new`:', function () { - var collection = Views(); - assert(collection instanceof Views); - }); - }); - - describe('static methods', function () { - it('should expose `extend`:', function () { - assert(typeof Views.extend ==='function'); - }); - }); - - describe('prototype methods', function () { - beforeEach(function() { - collection = new Views(); - }); - - var methods = [ - 'use', - 'setView', - 'addView', - 'addViews', - 'addList', - 'getView', - 'constructor', - 'set', - 'get', - 'del', - 'define', - 'visit', - 'on', - 'once', - 'off', - 'emit', - 'listeners', - 'hasListeners' - ]; - - methods.forEach(function (method) { - it('should expose ' + method + ' method', function () { - assert(typeof collection[method] === 'function'); - }); - }); - - it('should expose isCollection property', function () { - assert(typeof collection.isCollection === 'boolean'); - }); - - it('should expose queue property', function () { - assert(Array.isArray(collection.queue)); - }); - - it('should expose views property', function () { - assert(typeOf(collection.views) === 'object'); - }); - - it('should expose options property', function () { - assert(typeOf(collection.options) === 'object'); - }); - }); - - describe('instance', function () { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a value on the instance:', function () { - collection.set('a', 'b'); - assert(collection.a ==='b'); - }); - - it('should get a value from the instance:', function () { - collection.set('a', 'b'); - assert(collection.get('a') ==='b'); - }); - }); - - describe('option', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should set a key/value pair on options:', function () { - collection.option('a', 'b'); - assert(collection.options.a === 'b'); - }); - - it('should set an object on options:', function () { - collection.option({c: 'd'}); - assert(collection.options.c === 'd'); - }); - - it('should get an option:', function () { - collection.option({c: 'd'}); - var c = collection.option('c'); - assert(c === 'd'); - }); - }); - - describe('addView', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should throw an error when args are invalid:', function () { - (function () { - collection.addView(function() {}); - }).should.throw('expected value to be an object.'); - }); - - it('should add a view to `views`:', function () { - collection.addView('foo'); - collection.views.should.have.property('foo'); - - collection.addView('one', {content: '...'}); - assert(typeof collection.views.one === 'object'); - assert(isBuffer(collection.views.one.contents)); - }); - - it('should create an instance of `View`:', function () { - collection.addView('one', {content: '...'}); - assert(collection.views.one instanceof collection.View); - }); - - it('should allow an `View` constructor to be passed:', function () { - View.prototype.foo = function(key, value) { - this[key] = value; - }; - collection = new Views({View: View}); - collection.addView('one', {content: '...'}); - collection.views.one.foo('bar', 'baz'); - assert(collection.views.one.bar === 'baz'); - }); - - it('should allow an instance of `View` to be passed:', function () { - var collection = new Views({View: View}); - var view = new View({content: '...'}); - collection.addView('one', view); - view.set('abc', 'xyz'); - assert(collection.views.one instanceof collection.View); - assert(isBuffer(collection.views.one.contents)); - assert(collection.views.one.abc === 'xyz'); - }); - }); - - describe('addViews', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function (done) { - try { - collection.addViews('*.js'); - done(new Error('expected an error')); - } catch(err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function (done) { - try { - collection.addViews(['*.js']); - done(new Error('expected an error')); - } catch(err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add multiple views:', function () { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should return the collection instance for chaining:', function () { - var views = collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - - var view = views.getView('one'); - assert(view); - assert(view.content); - assert(view.content === 'foo'); - }); - - it('should create views from an instance of Views', function () { - collection.addViews({ - one: {content: 'foo'}, - two: {content: 'bar'} - }); - var pages = new Views(collection); - assert(isBuffer(pages.views.one.contents)); - assert(isBuffer(pages.views.two.contents)); - }); - - it('should add an array of views:', function () { - collection.addViews([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - }); - - describe('view', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should return a single collection view from a key-value pair', function () { - var one = collection.view('one', {content: 'foo'}); - var two = collection.view('two', {content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - - it('should return a single collection view from an object', function () { - var one = collection.view({path: 'one', content: 'foo'}); - var two = collection.view({path: 'two', content: 'bar'}); - - assert(one.isView); - assert(one.path === 'one'); - assert(two.isView); - assert(two.path === 'two'); - }); - }); - - describe('addList', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should emit an error if a string glob pattern is passed', function (done) { - try { - collection.addList('*.js'); - done(new Error('expected an error')); - } catch(err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should emit an error if an array glob pattern is passed', function (done) { - try { - collection.addList(['*.js']); - done(new Error('expected an error')); - } catch(err) { - assert(err); - assert(err.message); - assert(/glob/.test(err.message)); - done(); - } - }); - - it('should add a list of views:', function () { - collection.addList([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add a list from the constructor:', function () { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should add list items from the constructor:', function () { - var list = new List([ - {path: 'one', content: 'foo'}, - {path: 'two', content: 'bar'} - ]); - - collection = new Views(list.items); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.views.two.contents)); - }); - - it('should throw an error when list is not an array:', function () { - var views = new Views(); - (function () { - views.addList(); - }).should.throw('expected list to be an array.'); - - (function () { - views.addList({}); - }).should.throw('expected list to be an array.'); - - (function () { - views.addList('foo'); - }).should.throw('expected list to be an array.'); - }); - - it('should load an array of items from an event:', function () { - var pages = new Views(); - - pages.on('addList', function (list) { - while (list.length) { - pages.addView({path: list.pop()}); - } - this.loaded = true; - }); - - pages.addList(['a.txt', 'b.txt', 'c.txt']); - assert(pages.views.hasOwnProperty('a.txt')); - assert(pages.views['a.txt'].path === 'a.txt'); - }); - - it('should load an array of items from the addList callback:', function () { - var collection = new Views(); - - collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) { - return {path: fp}; - }); - assert(collection.views.hasOwnProperty('a.txt')); - assert(collection.views['a.txt'].path === 'a.txt'); - }); - - it('should load an object of views from an event:', function () { - var collection = new Views(); - - collection.on('addViews', function (views) { - for (var key in views) { - collection.addView('foo/' + key, views[key]); - delete views[key]; - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - - it('should signal `loaded` when finished:', function () { - var collection = new Views(); - - collection.on('addViews', function (views) { - for (var key in views) { - if (key === 'c') break; - collection.addView('foo/' + key, views[key]); - } - }); - - collection.addViews({ - a: {path: 'a.txt'}, - b: {path: 'b.txt'}, - c: {path: 'c.txt'} - }); - - assert(collection.views.hasOwnProperty('foo/a')); - assert(!collection.views.hasOwnProperty('foo/c')); - assert(collection.views['foo/a'].path === 'a.txt'); - }); - }); - - describe('getView', function() { - beforeEach(function() { - collection = new Views(); - }); - it('should get a view from `views`:', function () { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(isBuffer(collection.views.one.contents)); - assert(isBuffer(collection.getView('one').contents)); - assert(collection.getView('one').contents.toString() === 'aaa'); - assert(collection.getView('two').contents.toString() === 'zzz'); - }); - }); - - describe('count', function() { - beforeEach(function() { - collection = new Views(); - }); - - it('should get the number of views:', function () { - collection.addView('one', {content: 'aaa'}); - collection.addView('two', {content: 'zzz'}); - assert(Object.keys(collection.views).length === 2); - }); - }); -}); - -describe('options', function() { - describe('options.renameKey', function() { - beforeEach(function() { - collection = new Views({ - renameKey: function (key) { - return path.basename(key); - } - }); - }); - - it('should use a custom rename key function on view keys', function() { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the renamed key:', function () { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz'); - }); - - it('should get a view with the original key:', function () { - collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'}); - assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz'); - }); - }); -}); - - -describe('queue', function () { - beforeEach(function () { - collection = new Views(); - }); - - it('should emit arguments on addView', function (done) { - collection.on('addView', function (args) { - assert(args[0] === 'a'); - assert(args[1] === 'b'); - assert(args[2] === 'c'); - assert(args[3] === 'd'); - assert(args[4] === 'e'); - done(); - }); - - collection.addView('a', 'b', 'c', 'd', 'e'); - }); - - it('should expose the `queue` property for loading views', function () { - collection.queue.push(collection.view('b', {path: 'b'})); - - collection.addView('a', {path: 'a'}); - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - }); - - it('should load all views on the queue when addView is called', function () { - collection.on('addView', function (args) { - var len = args.length; - var last = args[len - 1]; - if (typeof last === 'string') { - args[len - 1] = { content: last }; - } - }); - - collection.addView('a.html', 'aaa'); - collection.addView('b.html', 'bbb'); - collection.addView('c.html', 'ccc'); - - assert(collection.views.hasOwnProperty('a.html')); - assert(collection.getView('a.html').content === 'aaa'); - assert(collection.views.hasOwnProperty('b.html')); - assert(collection.getView('b.html').content === 'bbb'); - assert(collection.views.hasOwnProperty('c.html')); - assert(collection.getView('c.html').content === 'ccc'); - }); -}); \ No newline at end of file diff --git a/test/views.use.js b/test/views.use.js deleted file mode 100644 index 09d47df..0000000 --- a/test/views.use.js +++ /dev/null @@ -1,156 +0,0 @@ -require('mocha'); -require('should'); -var assert = require('assert'); -var support = require('./support'); -var App = support.resolve(); -var Views = App.Views; -var View = App.View; -var collection; - -describe('views.use', function () { - beforeEach(function () { - collection = new Views(); - }); - - it('should expose the instance to `use`:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should be chainable:', function (done) { - collection.use(function (inst) { - assert(inst instanceof Views); - }) - .use(function (inst) { - assert(inst instanceof Views); - }) - .use(function (inst) { - assert(inst instanceof Views); - done(); - }); - }); - - it('should expose the collection to a plugin:', function () { - collection.use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }); - - collection.foo('a', {content: '...'}); - assert(collection.views.hasOwnProperty('a')); - }); - - it('should expose collection when chained:', function () { - collection - .use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function (views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function (views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should work when a custom `View` constructor is passed:', function () { - collection = new Views({View: require('vinyl')}); - collection - .use(function (views) { - assert(views instanceof Views); - views.foo = views.addView.bind(views); - }) - .use(function (views) { - assert(views instanceof Views); - views.bar = views.addView.bind(views); - }) - .use(function (views) { - assert(views instanceof Views); - views.baz = views.addView.bind(views); - }); - - var pages = collection; - - pages.foo({path: 'a', content: '...'}); - pages.bar({path: 'b', content: '...'}); - pages.baz({path: 'c', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - }); - - it('should pass to view `use` if a function is returned:', function () { - collection.use(function (views) { - assert(views instanceof Views); - - return function (view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .foo({path: 'c', content: '...'}) - .foo({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); - - it('should be chainable when a view function is returned:', function () { - collection - .use(function (views) { - assert(views instanceof Views); - - return function (view) { - view.foo = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function (views) { - assert(views instanceof Views); - - return function (view) { - view.bar = views.addView.bind(views); - assert(view instanceof View); - }; - }) - .use(function (views) { - assert(views instanceof Views); - - return function (view) { - view.baz = views.addView.bind(views); - assert(view instanceof View); - }; - }); - - collection.addView('a', {content: '...'}) - .foo({path: 'b', content: '...'}) - .bar({path: 'c', content: '...'}) - .baz({path: 'd', content: '...'}); - - assert(collection.views.hasOwnProperty('a')); - assert(collection.views.hasOwnProperty('b')); - assert(collection.views.hasOwnProperty('c')); - assert(collection.views.hasOwnProperty('d')); - }); -}); diff --git a/updatefile.js b/updatefile.js deleted file mode 100644 index e3004c0..0000000 --- a/updatefile.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports = function (app) { - app.task('jshint', require('./lib/tasks/jshint')); -};