diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0ca71b0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,14 @@ +.*.swp +._* +.DS_Store +.git +.hg +.npmrc +.lock-wscript +.svn +.wafpickle-* +config.gypi +CVS +npm-debug.log +.travis.yml +tmp diff --git a/.travis.yml b/.travis.yml index be642ae..6994ee2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,8 @@ -sudo: false language: node_js node_js: + - '6' - '5' - '5.1' - - '4' - '4.2' - '4.1' - '4.0' - - '0.12' diff --git a/LICENSE b/LICENSE index 0bd5ff3..01877d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Mendix +Copyright (c) 2015-2016 Mendix Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ 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. \ No newline at end of file +THE SOFTWARE. diff --git a/README.md b/README.md index 28e2f93..6d8feed 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,50 @@ ## About -This generator uses the Yeoman scaffolding tool to let you quickly create a [Mendix widget](https://world.mendix.com/display/public/howto50/Custom+Widget+Development) based on the latest [AppStoreWidgetBoilerPlate](https://github.com/mendix/AppStoreWidgetBoilerplate). +This generator uses the Yeoman scaffolding tool to let you quickly create a [Mendix widget](https://world.mendix.com/display/public/howto50/Custom+Widget+Development) based on the latest [AppStoreWidgetBoilerPlate](https://github.com/mendix/AppStoreWidgetBoilerplate). You can either use the full boilerplate with example code, or choose to use an empty widget. -### Prerequisites (you only have to do this ONCE) +If you want to see a short demo (this uses the older 1.x widget generator and only Grunt), please look at our [webinar](http://ww2.mendix.com/expert-webinar-developing-widgets.html) -First, you need to have NodeJs installed. After that, you need to install Yeoman, Mendix Widget generator and Grunt: +--- + +## Prerequisites _(you only have to do this once)_ + +First, you need to have [Node.js](https://nodejs.org/en/) installed. (We recommend a 4.x LTS version, the widget is tested against version 4 to 6). After that, you need to install Yeoman and the Mendix Widget generator: + +Open a command-line window (Press Win+R and type ``cmd`` or use Powershell) ```bash -npm install -g yo generator-mendix grunt-cli + npm install -g yo generator-mendix ``` -Make sure you have the latest version of ``yo``. The version we work with currently is 1.8.3 (which you can check by running ``yo --version``) +Make sure you have the latest version of ``yo``. The version we work with currently is 1.8.5 (which you can check by running ``yo --version``) + +Next, based on your preference, you need to install either Gulp (**recommended**) or Grunt. Install this by typing: -### Usage +```bash + # If you want to use Gulp, run: + + npm install -g gulp-cli -##### 1.) Start the generator in the folder you want to create a widget: + # If you want to use Grunt, run: + + npm install -g grunt-cli + +``` + +--- + +## Scaffold a widget + +### 1. Start the generator in the folder you want to create a widget ```bash yo mendix ``` -##### 2.) The generator will ask you to provide the following information about your widget: +### 2. Provide the following information about your widget: + +The following information needs to be provided about your widget: * name * description @@ -36,72 +59,137 @@ yo mendix * author * Github username (optional) -##### 3.) Your widget will be created using the options and the boilerplate. +You can press \ if you want to use the default values. -It will clone the boilerplate, rename your widget according to the options. It also includes a ``Gruntfile.js`` and ``package.json`` for development purposes (see below) +### 3.1. Which task runner do you want to use? -### Grunt +#### Gulp -The generator will include Grunt to automate your widget development. [Make sure you have Grunt installed](http://gruntjs.com/getting-started). +The widget generator will include a **Gulpfile.js** and the necessary package.json for running Gulp tasks. We recommend using Gulp because of the speed. -The following Grunt tasks can be started by typing ``grunt ``: +#### Grunt -* ``start-modeler`` +Earlier versions of the widget generator added Grunt as the default taskrunner. We included this option as well if you want to use this. -This will try to open the Modeler using the included test-project. (Older versions of the Gruntfile will use ``grunt start-mendix``) +### 3.2. Which template do you want to use? -* ``watch`` (this is actually an alias for default, so you can run ``grunt`` without adding this taskname) +#### AppStoreWidgetBoilerplate -This watches for changes in your ``src`` folder. When a file is changed, it copies the change to the deployment-folder (so you do not have to restart your project when changing files **(with the exception of ``.xml`` files)**). It also automatically creates a ``.mpk`` file in your ``/dist`` and ``test/widgets`` folder. +This uses the standard boilerplate which is on Github. This is recommended if you are a beginner. It includes example code with jQuery* and templates. -* ``version`` +#### Empty widget -This will let you set the version of your widget, the ``package.xml``, without editing it yourself. +The empty widget will use a slim version of the AppStoreWidgetBoilerplate. It only provides the essential methods and setup. Furthermore, it will ask you if you want to use templates and jQuery*. -* ``folders`` +\* **We do not recommend using jQuery in your widgets, please [read this issue](https://github.com/mendix/AppStoreWidgetBoilerplate/issues/38). For simple DOM-manipulation you can use Dojo, which is provided by Mendix. Only use jQuery when you need it for certain external libraries that depend on it.** + +--- + +## Use a taskrunner for development + +The widget generator will scaffold a widget for you. It provides a convenient setup to develop you widget and publish it on Github. To make it even more easy, we add files for taskrunners. + + +### Gulp + +The following tasks are provided in the **Gulpfile.js** and can be started by running ``gulp `` or ``npm run ``: + +* ``default`` -Grunt uses the settings in package.json to determine which folders it uses. If, for example, you use this to develop a widget in your project, you can change the folder in package.json: +You can omit this taskname and just run ``gulp``. This will watch for any changes in your ``src`` directory. If a change occurs, it will automatically create a new **MPK** and copy this to your ``widgets`` directory. + +It will also copy any changed Javascript files to your deployment folder. This means you do not have to redeploy locally, you can just refresh your browser. For changes in ``XML`` or ``CSS`` you will still need to synchronize your project directory (press F4 in the modeler) and redeploy. + +* ``modeler`` + +This will try to start the test-project that is included in the ``test`` folder. It automatically opens the Modeler with this project. If you have your test-project in a different location, you can change the following options in ``package.json``: ```json -... + "paths": { "testProjectFolder": "./test/", -... + "testProjectFileName": "Test.mpr" + }, ``` -and then check if the path is correct by running the ``grunt folders`` task +Note: If you provide a different path to the test-project in Windows, make sure you escape backslashes. So for example: ```C:\Projects\TestFolder``` needs to be properly written in the JSON as: + +```json + "testProjectFileName": "C:\\Projects\\TestFolder" +``` + +* ``version`` + +This task will show you the version number of the widget by reading the ``packages.xml`` file. You can set the version number with this task by typing: + +``gulp version -n=X.X.X`` or ``npm run version -- -n=X.X.X`` + +* ``folders`` + +This will tell you which folders Gulp is using. You can change the folder of the test-project folder (See ``modeler`` task) * ``build`` -Cleans old ``.mpk`` files and creates a new one in your ``/dist`` and ``test/widgets`` folder +This will build the widget for you. It will output a new **MPK** file to both your ``dist`` folder, as well as the ``widgets`` folder in your test-project directory. -* ``csslint`` +* ``icon`` -Lints through all CSS files that are in the ``widget/ui`` folder and checks for errors +This task will read the ``icon.png`` file (or any other image file if you provide the task a filename using ``gulp icon --file=``) and outputs a base64 string that you can use in your widget.xml. -### Using Grunt in a widget respository/project you downloaded. -If you download or clone one of our repositories that has a ``Gruntfile.js`` and ``package.json`` included, you need to install the dependencies first to be able to run Grunt: +### Grunt -1. Make sure you open the root folder in your command-line/terminal window -2. Run ``npm install`` to install the dependencies -3. Now you can use the Grunt tasks as described in the previous point +The tasks that are provided by Gulp are also provided in the **Gruntfile.js**. The following tasks are configured: -### Grunt can be started from command-line, or used by Grunt-plugins for different IDE's: +* ``start-modeler`` : same as ``gulp modeler`` +* ``build`` +* ``version`` and ``version:X.X.X`` +* ``folders`` +* ``icon``* +* ``watch`` : is the same as ``gulp default`` -* [WebStorm](https://www.jetbrains.com/webstorm/help/using-grunt-task-runner.html) -* [Brackets](https://github.com/dhategan/brackets-grunt) -* Visual Studio Code (has [built-in support for Grunt & Gulp](https://code.visualstudio.com/Docs/editor/tasks)) -* [Sublime Text](https://github.com/tvooo/sublime-grunt) -* NetBeans (has [built-in support for Grunt](https://blogs.oracle.com/geertjan/entry/grunting_in_netbeans_ide)) +\*This task can only read ``icon.png``, but it will automatically set the base64 string in your widget.xml for you. -## TODO: +--- -* Gulp integration -* Add JSHint (Grunt/Gulp) +## Use a taskrunner in an existing widget + +* _My widget repository does not have a ``Gruntfile.js`` or ``Gulpfile.js``._ + +We thought about that. Make sure you open a command-line terminal in your widget repository. It needs at least the widget files in the ``src`` folder. By simply running the generator there, it will ask you if you want to install a taskrunner. It also needs two parameters (widget name and version number) that it tries to read from the package.xml and widget.xml + +* _My widget repository contains a package.json and ``Gruntfile.js`` or ``Gulpfile.js``_ + +Great! If you have made sure you have Grunt/Gulp installed (See above, prerequisites), you can start using it. The only thing you need to do is install the dependencies for the different tasks. Run the same folder: + +```bash + npm install +``` + +Now you can use the Grunt/Gulp tasks as described. + +--- ## Troubleshooting +* _I get an error that it cannot find a dependency or local Grunt/Gulp._ + +Did you make sure you have the dependencies installed? Run ``npm install`` in your widget folder (it needs to have a package.json and **Gulpfile.js**/**Gruntfile.js**) + +* _Some of the tasks cannot be found_ +Check the version of your **Gruntfile.js**/**Gulpfile.js** (stated in the top of the file). The tasks described here are written for the 2.0 version of the widget generator. Any previous versions are outdated. If you run the widget generator in your widget folder again, it will update the file and dependencies for you. + +* _I am getting weird errors_ + +Please report your issues [here](https://github.com/mendix/generator-mendix/issues). and we'll troubleshoot together with you. + +--- + +## TODO: + +* Add LICENSE files [request](https://github.com/mendix/generator-mendix/issues/19) +* Add JSHint (Grunt/Gulp) +* Add SASS support (add ``_sass`` folder that will output CSS to your src folder) ## Issues diff --git a/generators/app/index.js b/generators/app/index.js index 13991b2..c3f89d9 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -2,30 +2,19 @@ 'use strict'; var pkg = require(__dirname + '/../../package.json'); -var semver = require('semver'); var fs = require('fs'); var extfs = require('extfs'); var xml2js = require("xml2js"); var parser = new xml2js.Parser(); var yeoman = require('yeoman-generator'); -var chalk = require('chalk'); -var boilerPlatePath = 'AppStoreWidgetBoilerplate/'; +var promptTexts = require('./lib/prompttexts.js'); +var text = require('./lib/text.js'); -var banner = [ - '', - chalk.bold.cyan(' __ ____ __') + ' _ _ _ ', - chalk.bold.cyan(' | \\/ \\ \\ / /') + ' (_) | | | | ', - chalk.bold.cyan(' | \\ / |\\ V / ') + ' __ ___ __| | __ _ ___| |_ ', - chalk.bold.cyan(' | |\\/| | > < ') + ' \\ \\ /\\ / / |/ _` |/ _` |/ _ \\ __| ', - chalk.bold.cyan(' | | | |/ . \\ ') + ' \\ V V /| | (_| | (_| | __/ |_ ', - chalk.bold.cyan(' |_| |_/_/ \\_\\') + ' \\_/\\_/ |_|\\__,_|\\__, |\\___|\\__| ', - ' __/ | ', - ' |___/ ', - ' Generator, version: ' + pkg.version, - ' Issues? Please report them at : ' + chalk.cyan(pkg.bugs.url), - '' -].join('\n'); +var boilerPlatePath = 'AppStoreWidgetBoilerplate/', + emptyBoilerplatePath = 'WidgetBoilerplate/'; + +var banner = text.getBanner(pkg); module.exports = yeoman.Base.extend({ constructor: function () { @@ -33,6 +22,8 @@ module.exports = yeoman.Base.extend({ var done = this.async(); this.isNew = true; + this.FINISHED = false; + this.folders = extfs.getDirsSync(this.destinationRoot()); this.current = {}; this.current.version = '1.0.0'; @@ -50,9 +41,10 @@ module.exports = yeoman.Base.extend({ this.current.author = destPkg.author; this.current.copyright = destPkg.copyright; this.current.license = destPkg.license; + this.current.builder = typeof destPkg.devDependencies.grunt !== "undefined" ? 'grunt' : 'gulp'; } catch (e) { - console.error("Error reading package.json. Please check the file or remove it before you run the generator again. Error: " + e.toString()); - process.exit(1); + console.error(text.PACKAGE_READ_ERROR + e.toString()); + this.FINISHED = true; done(); return; } } if (!extfs.isEmptySync(this.destinationPath('src/package.xml'))) { @@ -61,7 +53,7 @@ module.exports = yeoman.Base.extend({ parser.parseString(pkgXml, function (err, result) { if (err) { this.log('Error: ' + err); - process.exit(0); + this.FINISHED = true; done(); return; } if (result.package.clientModule[0]["$"]["version"]) { var version = result.package.clientModule[0]["$"]["version"]; @@ -77,7 +69,9 @@ module.exports = yeoman.Base.extend({ done(); } } else if (!extfs.isEmptySync(this.destinationRoot())) { - this.isNew = false; + this.log(banner); + this.log(text.DIR_NOT_EMPTY_ERROR); + this.FINISHED = true; done(); } else { done(); @@ -86,103 +80,24 @@ module.exports = yeoman.Base.extend({ prompting: function () { var done = this.async(); + if (this.FINISHED) { + done(); + return; + } + // Have Yeoman greet the user. this.log(banner); - var promptsNew = [ - { - type: 'input', - name: 'widgetName', - validate: function (input) { - if (/^([a-zA-Z]*)$/.test(input)) { return true; } - return 'Your widget can only contain letters (a-z & A-Z). Please provide a valid name'; - }, - message: 'What is name of your widget?', - default: 'MyWidget' - },{ - type: 'input', - name: 'description', - message: 'Enter a description for your widget', - default: 'My brand new Mendix widget' - },{ - type: 'input', - name: 'copyright', - message: 'Add a copyright', - default: ' 2016', - store: true - },{ - type: 'input', - name: 'license', - message: 'Add a license', - default: 'Apache 2', - store: true - },{ - type: 'input', - name: 'version', - validate: function (input) { - if (semver.valid(input) && semver.satisfies(input, '>=1.0.0')) { - return true; - } - return 'Your version needs to be formatted as x.x.x and starts at 1.0.0. Using 1.0.0'; - }, - message: 'Initial version', - default: '1.0.0' - },{ - type: 'input', - name: 'author', - message: 'Author', - default: '', - store: true - } - ]; - - var promptsUpgrade = [ - { - type: 'confirm', - name: 'upgrade', - message: 'Are you upgrading a custom widget? (Need \'src\' folder to work)', - default: false - },{ - type: 'input', - name: 'widgetName', - validate: function (input) { - if (/^([a-zA-Z]*)$/.test(input)) { return true; } - return 'Your widget can only contain letters (a-z & A-Z). Please provide a valid name'; - }, - message: 'What is name of your widget? (Please make it the same as your current MPK file, e.g. "CustomWidget.mpk", name is "CustomWidget")', - default: this.current.name, - when: function (props) { - return props.upgrade; - } - },{ - type: 'input', - name: 'version', - validate: function (input) { - if (semver.valid(input) && semver.satisfies(input, '>=1.0.0')) { - return true; - } - return 'Your version needs to be formatted as x.x.x and starts at 1.0.0. Using 1.0.0'; - }, - message: 'Enter your current version (package.xml) or the default version', - default: this.current.version, - when: function (props) { - return props.upgrade; - } - } - ]; - if (this.isNew) { this - .prompt(promptsNew) + .prompt(promptTexts.promptsNew()) .then(function (props) { this.props = props; - // To access props later use this.props.someOption; done(); }.bind(this)); } else { - this.log(chalk.bold.red(' The directory is not empty. If you are creating a new widget, please open the generator in an empty folder (Press Ctrl+C to abort)\n\n')); this - .prompt(promptsUpgrade) + .prompt(promptTexts.promptsUpgrade(this.current)) .then(function (props) { this.props = props; if (!props.upgrade) { @@ -192,11 +107,13 @@ module.exports = yeoman.Base.extend({ } }.bind(this)); } - }, writing: { app: function () { + if (this.FINISHED) { + return; + } // Define widget variables this.widget = {}; this.widget.widgetName = this.props.widgetName; @@ -209,55 +126,76 @@ module.exports = yeoman.Base.extend({ this.widget.license = this.props.license || this.current.license; this.widget.generatorVersion = pkg.version; - // Using grunt (future version will include Gulp) - this.widget.builder = 'grunt'; + this.widget.builder = this.props.builder; if (this.isNew) { + var source = this.props.boilerplate === 'appstore' ? boilerPlatePath : emptyBoilerplatePath; + this.props.widgetOptionsObj = {}; + if (this.props.boilerplate === 'empty') { + for (var i = 0; i < this.props.widgetoptions.length; i++) { + this.props.widgetOptionsObj[this.props.widgetoptions[i]] = true; + } + } + // Copy generic files this.fs.copy(this.templatePath('icon.png'), this.destinationPath('icon.png')); this.fs.copy(this.templatePath(boilerPlatePath + 'assets/app_store_banner.png'), this.destinationPath('assets/app_store_banner.png')); this.fs.copy(this.templatePath(boilerPlatePath + 'assets/app_store_icon.png'), this.destinationPath('assets/app_store_icon.png')); - this.fs.copy(this.templatePath(boilerPlatePath + 'LICENSE'), this.destinationPath('LICENSE')); - this.fs.copy(this.templatePath(boilerPlatePath + 'README.md'), this.destinationPath('README.md')); + //this.fs.copy(this.templatePath(source + 'LICENSE'), this.destinationPath('LICENSE')); + this.fs.copy(this.templatePath(source + 'README.md'), this.destinationPath('README.md')); this.fs.copy(this.templatePath(boilerPlatePath + 'test/Test.mpr'), this.destinationPath('test/Test.mpr')); this.fs.copy(this.templatePath(boilerPlatePath + 'xsd/widget.xsd'), this.destinationPath('xsd/widget.xsd')); // Copy files based on WidgetName - this.fs.copy( - this.templatePath(boilerPlatePath + 'src/WidgetName/lib/jquery-1.11.2.js'), - this.destinationPath('src/' + this.widget.widgetName + '/lib/jquery-1.11.2.js') - ); - this.fs.copy( - this.templatePath(boilerPlatePath + 'src/WidgetName/widget/template/WidgetName.html'), - this.destinationPath('src/' + this.widget.widgetName + '/widget/template/' + this.widget.widgetName + '.html') - ); + if (this.props.boilerplate === 'appstore' || this.props.widgetOptionsObj.jquery) { + this.fs.copy( + this.templatePath(boilerPlatePath + 'src/WidgetName/lib/jquery-1.11.2.js'), + this.destinationPath('src/' + this.widget.widgetName + '/lib/jquery-1.11.2.js') + ); + } + + if (this.props.boilerplate === 'appstore' || this.props.widgetOptionsObj.templates) { + this.fs.copy( + this.templatePath(source + 'src/WidgetName/widget/template/WidgetName.html'), + this.destinationPath('src/' + this.widget.widgetName + '/widget/template/' + this.widget.widgetName + '.html') + ); + } this.fs.copy( - this.templatePath(boilerPlatePath + 'src/WidgetName/widget/ui/WidgetName.css'), + this.templatePath(source + 'src/WidgetName/widget/ui/WidgetName.css'), this.destinationPath('src/' + this.widget.widgetName + '/widget/ui/' + this.widget.widgetName + '.css') ); // Rename references in widget main JS - this.fs.copy( - this.templatePath(boilerPlatePath + 'src/WidgetName/widget/WidgetName.js'), - this.destinationPath('src/' + this.widget.widgetName + '/widget/' + this.widget.widgetName + '.js'), - { - process: function (file) { - var fileText = file.toString(); - fileText = fileText - .replace(/WidgetName\.widget\.WidgetName/g, this.widget.packageName + '.widget.' + this.widget.widgetName) - .replace(/WidgetName\/widget\/WidgetName/g, this.widget.packageName + '/widget/' + this.widget.widgetName) - .replace(/WidgetName/g, this.widget.widgetName) - .replace(/\{\{version\}\}/g, this.widget.version) - .replace(/\{\{date\}\}/g, this.widget.date) - .replace(/\{\{copyright\}\}/g, this.widget.copyright) - .replace(/\{\{license\}\}/g, this.widget.license) - .replace(/\{\{author\}\}/g, this.widget.author); - return fileText; - }.bind(this) - } - ); + if (this.props.boilerplate === 'appstore') { + this.fs.copy( + this.templatePath(source + 'src/WidgetName/widget/WidgetName.js'), + this.destinationPath('src/' + this.widget.widgetName + '/widget/' + this.widget.widgetName + '.js'), + { + process: function (file) { + var fileText = file.toString(); + fileText = fileText + .replace(/WidgetName\.widget\.WidgetName/g, this.widget.packageName + '.widget.' + this.widget.widgetName) + .replace(/WidgetName\/widget\/WidgetName/g, this.widget.packageName + '/widget/' + this.widget.widgetName) + .replace(/WidgetName/g, this.widget.widgetName) + .replace(/\{\{version\}\}/g, this.widget.version) + .replace(/\{\{date\}\}/g, this.widget.date) + .replace(/\{\{copyright\}\}/g, this.widget.copyright) + .replace(/\{\{license\}\}/g, this.widget.license) + .replace(/\{\{author\}\}/g, this.widget.author); + return fileText; + }.bind(this) + } + ); + } else { + this.widget.options = this.props.widgetOptionsObj; + this.template( + this.templatePath(source + 'src/WidgetName/widget/WidgetName.js.ejs'), + this.destinationPath('src/' + this.widget.widgetName + '/widget/' + this.widget.widgetName + '.js'), + this.widget + ); + } // Rename references package.xml this.fs.copy( @@ -276,7 +214,7 @@ module.exports = yeoman.Base.extend({ // Rename references WidgetName this.fs.copy( - this.templatePath(boilerPlatePath + 'src/WidgetName/WidgetName.xml'), + this.templatePath(source + 'src/WidgetName/WidgetName.xml'), this.destinationPath('src/' + this.widget.widgetName + '/' + this.widget.widgetName + '.xml'), { process: function (file) { @@ -294,17 +232,29 @@ module.exports = yeoman.Base.extend({ this.fs.copy(this.templatePath('_gitignore'), this.destinationPath('.gitignore')); // jshint - this.fs.copy(this.templatePath(boilerPlatePath + '.jshintrc'), this.destinationPath('.jshintrc')); + this.fs.copy(this.templatePath('_jshintrc'), this.destinationPath('.jshintrc')); // Package.JSON + try { extfs.removeSync(this.destinationPath('package.json')); } catch (e) {} this.template('_package.json', 'package.json', this.widget, {}); - // Add Gruntfile + // Add Gulp/Grunt this.pkg = pkg; - this.template('Gruntfile.js', 'Gruntfile.js', this, {}); + + try { extfs.removeSync(this.destinationPath('Gruntfile.js')); } catch (e) {} + try { extfs.removeSync(this.destinationPath('Gulpfile.js')); } catch (e) {} + + if (this.widget.builder === 'gulp') { + this.template('Gulpfile.js', 'Gulpfile.js', this, {}); + } else { + this.template('Gruntfile.js', 'Gruntfile.js', this, {}); + } }, projectfiles: function () { + if (this.FINISHED) { + return; + } this.fs.copy( this.templatePath('editorconfig'), this.destinationPath('.editorconfig') @@ -313,12 +263,22 @@ module.exports = yeoman.Base.extend({ }, install: function () { - this.log('Copied files, now running ' + chalk.cyan('npm install') + ' to install development dependencies'); + if (this.FINISHED) { + return; + } + this.log(text.INSTALL_FINISH_MSG); this.npmInstall(); }, end: function () { - this.log('\n\n> I will now run ' + chalk.cyan('grunt build') + ' to build the mpk (do this before starting the modeler)< \n\n'); - this.spawnCommand('grunt', ['build']); + if (this.FINISHED) { + return; + } + if (extfs.isEmptySync(this.destinationPath("node_modules"))) { + this.log(text.END_NPM_NEED_INSTALL_MSG); + } else { + this.log(text.END_RUN_BUILD_MSG); + this.spawnCommand('npm', ['run', 'build']); + } } }); diff --git a/generators/app/lib/prompttexts.js b/generators/app/lib/prompttexts.js new file mode 100644 index 0000000..6d9cf09 --- /dev/null +++ b/generators/app/lib/prompttexts.js @@ -0,0 +1,174 @@ +/*jshint -W108,-W069*/ +"use strict"; +var semver = require('semver'); + +function promptsNew () { + return [ + { + type: 'input', + name: 'widgetName', + validate: function (input) { + if (/^([a-zA-Z]*)$/.test(input)) { return true; } + return 'Your widget can only contain letters (a-z & A-Z). Please provide a valid name'; + }, + message: 'What is name of your widget?', + default: 'MyWidget' + },{ + type: 'input', + name: 'description', + message: 'Enter a description for your widget', + default: 'My brand new Mendix widget' + },{ + type: 'input', + name: 'copyright', + message: 'Add a copyright', + default: ' 2016', + store: true + },{ + type: 'input', + name: 'license', + message: 'Add a license', + default: 'Apache 2', + store: true + },{ + type: 'input', + name: 'version', + validate: function (input) { + if (semver.valid(input) && semver.satisfies(input, '>=1.0.0')) { + return true; + } + return 'Your version needs to be formatted as x.x.x and starts at 1.0.0. Using 1.0.0'; + }, + message: 'Initial version', + default: '1.0.0' + },{ + type: 'input', + name: 'author', + message: 'Author', + default: '', + store: true + },{ + type: 'list', + name: 'builder', + message: 'Which task runner do you want to use for development?', + choices: [{ + name: 'Gulp (recommended)', + value: 'gulp' + },{ + name: 'Grunt', + value: 'grunt' + } + ], + default: 0 + }, + /*{ + // NOT IMPLEMENTED YET + type: 'confirm', + name: 'useSass', + message: 'Do you want to use SASS? (will add _sass folder)', + default: false, + store: true, + when: function (props) { + return props.builder === 'gulp'; + }, + store: true + },*/ + { + type: 'list', + name: 'boilerplate', + message: 'Which template do you want to use for the widget?', + choices: [{ + name: 'AppStoreWidgetBoilerplate, from Github (recommended for beginners)', + value: 'appstore' + },{ + name: 'Empty widget (recommended for more experienced developers)', + value: 'empty' + } + ], + store: true + },{ + type: 'checkbox', + name: 'widgetoptions', + message: 'Which of the following things apply?', + choices: [{ + name: 'Use templates (add template mixin and template.html)', + value: 'templates', + checked: false + },{ + name: 'Use jQuery (not recommended, only use it if external libraries need it)', + value: 'jquery', + checked: false + }/* + // NOT IMPLEMENTED YET,{ + name: 'Add Execute Microflow shorthand', + value: 'execMf', + checked: false + },{ + name: 'Add validation (will add an attribute property and example methods, will use templates)', + value: 'validation', + checked: false + } + */ + ], + when: function (props) { + return props.boilerplate === 'empty'; + }, + store: true + } + ]; +} + +function promptsUpgrade (current) { + return [ + { + type: 'confirm', + name: 'upgrade', + message: 'Are you upgrading a custom widget? (Need \'src\' folder to work)', + default: false + },{ + type: 'input', + name: 'widgetName', + validate: function (input) { + if (/^([a-zA-Z]*)$/.test(input)) { return true; } + return 'Your widget can only contain letters (a-z & A-Z). Please provide a valid name'; + }, + message: 'What is name of your widget? (Do not change this, unless you know what you are doing)', + default: current.name, + when: function (props) { + return props.upgrade; + } + },{ + type: 'input', + name: 'version', + validate: function (input) { + if (semver.valid(input) && semver.satisfies(input, '>=1.0.0')) { + return true; + } + return 'Your version needs to be formatted as x.x.x and starts at 1.0.0. Using 1.0.0'; + }, + message: 'Enter your current version (package.xml) or the default version', + default: current.version, + when: function (props) { + return props.upgrade; + } + },{ + type: 'list', + name: 'builder', + message: 'Which build script do you want to use? (This will remove old Gruntfile/Gulpfile)', + choices: [{ + name: 'Gulp (recommended)', + value: 'gulp' + },{ + name: 'Grunt', + value: 'grunt' + } + ], + default: 0 + } + ]; +} + +module.exports = { + promptsNew: promptsNew, + promptsUpgrade: promptsUpgrade +}; diff --git a/generators/app/lib/text.js b/generators/app/lib/text.js new file mode 100644 index 0000000..5974470 --- /dev/null +++ b/generators/app/lib/text.js @@ -0,0 +1,30 @@ +/*jshint -W108,-W069*/ +"use strict"; + +var chalk = require('chalk'); + +module.exports = { + getBanner: function (pkg) { + return [ + '', + chalk.bold.cyan(' __ ____ __') + ' _ _ _ ', + chalk.bold.cyan(' | \\/ \\ \\ / /') + ' (_) | | | | ', + chalk.bold.cyan(' | \\ / |\\ V / ') + ' __ ___ __| | __ _ ___| |_ ', + chalk.bold.cyan(' | |\\/| | > < ') + ' \\ \\ /\\ / / |/ _` |/ _` |/ _ \\ __| ', + chalk.bold.cyan(' | | | |/ . \\ ') + ' \\ V V /| | (_| | (_| | __/ |_ ', + chalk.bold.cyan(' |_| |_/_/ \\_\\') + ' \\_/\\_/ |_|\\__,_|\\__, |\\___|\\__| ', + ' __/ | ', + ' |___/ ', + ' Generator, version: ' + pkg.version, + ' Issues? Please report them at : ' + chalk.cyan(pkg.bugs.url), + '' + ].join('\n'); + }, + + PACKAGE_READ_ERROR: "Error reading package.json. Please check the file or remove it before you run the generator again. Error: ", + DIR_NOT_EMPTY_ERROR: chalk.bold.red(' The directory is not empty and we cannot detect a widget.\n If you are creating a new widget, please open the generator in an empty folder.\n If you want to upgrade a widget, make sure you are using the generator in a widget folder.\n'), + + INSTALL_FINISH_MSG: 'Copied files, now running ' + chalk.cyan('npm install') + ' to install development dependencies', + END_NPM_NEED_INSTALL_MSG: '\n\n> Dependencies should be installed using ' + chalk.cyan('npm install') + ' before I can run the build using ' + chalk.cyan('npm run build') + ' < \n\n', + END_RUN_BUILD_MSG: '\n\n> I will now run ' + chalk.cyan('npm run build') + ' to build the mpk (do this before starting the modeler)< \n\n' +}; diff --git a/generators/app/templates/Gulpfile.js b/generators/app/templates/Gulpfile.js new file mode 100644 index 0000000..a61cf1b --- /dev/null +++ b/generators/app/templates/Gulpfile.js @@ -0,0 +1,86 @@ +// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %> :: <%= pkg.repository.url %> +/*jshint -W069,-W097*/ +"use strict"; + +// In case you seem to have trouble starting Mendix through `gulp modeler`, you might have to set the path to the Mendix application, otherwise leave both values as they are +var MODELER_PATH = null; +var MODELER_ARGS = "/file:{path}"; + +/******************************************************************************** + * Do not edit anything below, unless you know what you are doing + ********************************************************************************/ +var gulp = require("gulp"), + zip = require("gulp-zip"), + del = require("del"), + newer = require("gulp-newer"), + gutil = require("gulp-util"), + gulpif = require("gulp-if"), + jsonTransform = require("gulp-json-transform"), + intercept = require("gulp-intercept"), + argv = require("yargs").argv, + widgetBuilderHelper = require("widgetbuilder-gulp-helper"); + +var pkg = require("./package.json"), + paths = widgetBuilderHelper.generatePaths(pkg), + xmlversion = widgetBuilderHelper.xmlversion; + +gulp.task("default", function() { + gulp.watch("./src/**/*", ["compress"]); + gulp.watch("./src/**/*.js", ["copy:js"]); +}); + +gulp.task("clean", function () { + return del([ + paths.WIDGET_TEST_DEST, + paths.WIDGET_DIST_DEST + ], { force: true }); +}); + +gulp.task("compress", ["clean"], function () { + return gulp.src("src/**/*") + .pipe(zip(pkg.name + ".mpk")) + .pipe(gulp.dest(paths.TEST_WIDGETS_FOLDER)) + .pipe(gulp.dest("dist")); +}); + +gulp.task("copy:js", function () { + return gulp.src(["./src/**/*.js"]) + .pipe(newer(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)) + .pipe(gulp.dest(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)); +}); + +gulp.task("version:xml", function () { + return gulp.src(paths.PACKAGE_XML) + .pipe(xmlversion(argv.n)) + .pipe(gulp.dest("./src/")); +}); + +gulp.task("version:json", function () { + return gulp.src("./package.json") + .pipe(gulpif(typeof argv.n !== "undefined", jsonTransform(function(data) { + data.version = argv.n; + return data; + }, 2))) + .pipe(gulp.dest("./")); +}); + +gulp.task("icon", function (cb) { + var icon = (typeof argv.file !== "undefined") ? argv.file : "./icon.png"; + console.log("\nUsing this file to create a base64 string: " + gutil.colors.cyan(icon)); + gulp.src(icon) + .pipe(intercept(function (file) { + console.log("\nCopy the following to your " + pkg.name + ".xml (after description):\n\n" + gutil.colors.cyan("") + file.contents.toString("base64") + gutil.colors.cyan("<\\icon>") + "\n"); + cb(); + })); +}); + +gulp.task("folders", function () { + paths.showPaths(); return; +}); + +gulp.task("modeler", function (cb) { + widgetBuilderHelper.runmodeler(MODELER_PATH, MODELER_ARGS, paths.TEST_PATH, cb); +}); + +gulp.task("build", ["compress"]); +gulp.task("version", ["version:xml", "version:json"]); diff --git a/generators/app/templates/WidgetBoilerplate/README.md b/generators/app/templates/WidgetBoilerplate/README.md new file mode 100644 index 0000000..78dde32 --- /dev/null +++ b/generators/app/templates/WidgetBoilerplate/README.md @@ -0,0 +1,3 @@ +# Mendix Widget Boilerplate + +See [AppStoreWidgetBoilerplate](https://github.com/mendix/AppStoreWidgetBoilerplate/) for an example diff --git a/generators/app/templates/WidgetBoilerplate/src/WidgetName/WidgetName.xml b/generators/app/templates/WidgetBoilerplate/src/WidgetName/WidgetName.xml new file mode 100644 index 0000000..1a63424 --- /dev/null +++ b/generators/app/templates/WidgetBoilerplate/src/WidgetName/WidgetName.xml @@ -0,0 +1,15 @@ + + + WidgetName + The description of this widget. + + + + + + Dummy key + Appearance + This key was added to make the widget work in the Modeler (needs atleast 1 key). Please change/remove this one + + + diff --git a/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/WidgetName.js.ejs b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/WidgetName.js.ejs new file mode 100644 index 0000000..09d1560 --- /dev/null +++ b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/WidgetName.js.ejs @@ -0,0 +1,74 @@ +define([ + "dojo/_base/declare", + "mxui/widget/_WidgetBase", +<% if (options.templates) { %> "dijit/_TemplatedMixin",<% + +} %> + "mxui/dom", + "dojo/dom", + "dojo/dom-prop", + "dojo/dom-geometry", + "dojo/dom-class", + "dojo/dom-style", + "dojo/dom-construct", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/text", + "dojo/html", + "dojo/_base/event", +<% if (options.jquery) { %> "<%= widgetName %>/lib/jquery-1.11.2",<% } %> +<% if (options.templates) { %> "dojo/text!<%= widgetName %>/widget/template/<%= widgetName %>.html"<% } %> +], function (declare, _WidgetBase, _TemplatedMixin, dom, dojoDom, dojoProp, dojoGeometry, dojoClass, dojoStyle, dojoConstruct, dojoArray, dojoLang, dojoText, dojoHtml, dojoEvent<% if (options.jquery) { %>, _jQuery<% } %><% if (options.templates) { %>, widgetTemplate<% } %>) { + "use strict"; +<% if (options.jquery) { %> + var $ = _jQuery.noConflict(true); +<% } %> + return declare("<%= packageName %>.widget.<%= widgetName %>", [ _WidgetBase<% if (options.templates) { %>, _TemplatedMixin<% } %> ], { +<% if (options.templates) { %> + templateString: widgetTemplate, +<% } %> +<% if (options.templates) { %> + widgetBase: null, +<% } %> + // Internal variables. + _handles: null, + _contextObj: null, + + constructor: function () { + this._handles = []; + }, + + postCreate: function () { + logger.debug(this.id + ".postCreate"); + }, + + update: function (obj, callback) { + logger.debug(this.id + ".update"); + + this._contextObj = obj; + this._updateRendering(callback); + }, + + resize: function (box) { + logger.debug(this.id + ".resize"); + }, + + uninitialize: function () { + logger.debug(this.id + ".uninitialize"); + }, + + _updateRendering: function (callback) { + logger.debug(this.id + "._updateRendering"); + + if (this._contextObj !== null) { + dojoStyle.set(this.domNode, "display", "block"); + } else { + dojoStyle.set(this.domNode, "display", "none"); + } + + mendix.lang.nullExec(callback); + } + }); +}); + +require(["<%= packageName %>/widget/<%= widgetName %>"]); diff --git a/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/template/WidgetName.html b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/template/WidgetName.html new file mode 100644 index 0000000..e409fca --- /dev/null +++ b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/template/WidgetName.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/ui/WidgetName.css b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/ui/WidgetName.css new file mode 100644 index 0000000..a708836 --- /dev/null +++ b/generators/app/templates/WidgetBoilerplate/src/WidgetName/widget/ui/WidgetName.css @@ -0,0 +1,3 @@ +.widgetname { + +} diff --git a/generators/app/templates/_jshintrc b/generators/app/templates/_jshintrc new file mode 100644 index 0000000..f73b569 --- /dev/null +++ b/generators/app/templates/_jshintrc @@ -0,0 +1,23 @@ +{ + // Enforcing + "curly" : false, + "forin" : false, + "latedef" : "nofunc", + "newcap" : true, + "quotmark" : "double", + "eqeqeq" : true, + "undef" : true, + "globals" : { + "mendix" : false, + "mx" : false, + "logger" : false + }, + + // Relaxing + "laxbreak" : true, + + // Environments + "browser" : true, + "devel" : true, + "dojo" : true +} diff --git a/generators/app/templates/_package.json b/generators/app/templates/_package.json index 25077bc..7314d27 100644 --- a/generators/app/templates/_package.json +++ b/generators/app/templates/_package.json @@ -1,28 +1,39 @@ { - "name": "<%= widgetName %>", - "version": "<%= version %>", - "description": "<%= description %>", - "license": "<%= license %>", - "author": "<%= author %>", + "name": "<%- widgetName %>", + "version": "<%- version %>", + "description": "<%- description %>", + "license": "<%- license %>", + "author": "<%- author %>", "private": true, "dependencies": { }, "devDependencies": {<% if (builder == 'grunt') { %> - "grunt": "1.0.1", + "grunt": "^1.0.1", "grunt-contrib-clean": "^1.0.0", - "grunt-contrib-compress": "^1.2.0", + "grunt-contrib-compress": "^1.3.0", "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-watch": "^1.0.0", "grunt-contrib-csslint": "^1.0.0", - "grunt-newer": "^1.1.1", - "node-base64-image": "^0.1.0", - "shelljs": "^0.7.0", - "xml2js": "^0.4.15", + "grunt-contrib-watch": "^1.0.0", + "grunt-newer": "^1.2.0", + "node-base64-image": "^0.1.2", + "node-mendix-modeler-path": "https://github.com/JelteMX/node-mendix-modeler-path/archive/v1.0.0.tar.gz", "semver": "^5.1.0", - "node-mendix-modeler-path": "https://github.com/JelteMX/node-mendix-modeler-path/archive/v1.0.0.tar.gz" - },<% } if (builder == 'gulp') { %><% } %> + "shelljs": "^0.7.4", + "xml2js": "^0.4.17" + },<% } if (builder == 'gulp') { %> + "del": "^2.2.2", + "gulp": "^3.9.1", + "gulp-if": "^2.0.1", + "gulp-intercept": "^0.1.0", + "gulp-json-transform": "^0.4.2", + "gulp-newer": "^1.3.0", + "gulp-util": "^3.0.7", + "gulp-zip": "^3.2.0", + "widgetbuilder-gulp-helper": "https://github.com/JelteMX/widgetbuilder-gulp-helper/archive/1.0.1.tar.gz", + "yargs": "^6.0.0" + },<% } %> "engines": { - "node": ">=0.12.0" + "node": ">=5" }, "generatorVersion": "<%= generatorVersion %>", "paths": { @@ -30,7 +41,11 @@ "testProjectFileName": "Test.mpr" }, "scripts": {<% if (builder == 'grunt') { %> - "test": "grunt test"<% } if (builder == 'gulp') { %> - <% } %> + "build": "grunt build"<% } if (builder == 'gulp') { %> + "build": "node ./node_modules/gulp/bin/gulp build", + "version": "node ./node_modules/gulp/bin/gulp version", + "icon": "node ./node_modules/gulp/bin/gulp icon", + "folders": "node ./node_modules/gulp/bin/gulp folders", + "modeler": "node ./node_modules/gulp/bin/gulp modeler"<% } %> } } diff --git a/package.json b/package.json index 85b91f3..3f7a6ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generator-mendix", - "version": "1.3.7", + "version": "2.0.0", "description": "Mendix Widget generator", "license": "MIT", "main": "app/index.js", @@ -32,15 +32,17 @@ "dependencies": { "chalk": "^1.0.0", "extfs": "0.0.7", - "semver": "^5.1.0", - "xml2js": "^0.4.15", - "yeoman-generator": "^0.23.3" + "semver": "^5.3.0", + "xml2js": "^0.4.17", + "yeoman-generator": "^0.24.1" }, "devDependencies": { - "mocha": "*", + "fs-extra": "^0.30.0", + "lodash": "^4.16.4", + "mocha": "3.1.0", "mock-spawn": "^0.2.6", "string-template": "1.0.0", "yeoman-assert": "^2.2.1", - "yeoman-test": "^1.4.0" + "yeoman-test": "^1.5.1" } } diff --git a/test/lib/prompt-options.js b/test/lib/prompt-options.js new file mode 100644 index 0000000..94ae272 --- /dev/null +++ b/test/lib/prompt-options.js @@ -0,0 +1,12 @@ +module.exports = function () { + "use strict"; + return { + widgetName: "TESTWIDGET", + description: "This is a test widget", + copyright: "TestCompany", + license: "MIT", + version: "2.0.0", + author: "TestAuthor", + boilerplate: "appstore" + }; +}; diff --git a/test/lib/testrunner-appstore-boilerplate.js b/test/lib/testrunner-appstore-boilerplate.js new file mode 100644 index 0000000..90108fc --- /dev/null +++ b/test/lib/testrunner-appstore-boilerplate.js @@ -0,0 +1,104 @@ +/*global it,before,describe*/ +/*jshint -W108,-W069*/ +'use strict'; + +var path = require('path'); +var assert = require('yeoman-assert'); +var helpers = require('yeoman-test'); +var format = require('string-template'); +var mockSpawn = require('mock-spawn'); +//var fs = require("fs"); + +module.exports = function (builder) { + var promptOps = require('./prompt-options.js')(); + promptOps.widgetName = 'TestWidgetForTHE' + builder; + promptOps.builder = builder; + + describe(format('Generator with {0}:\n', builder), function () { + + var mySpawn = mockSpawn(); + require('child_process').spawn = mySpawn; + + mySpawn.setDefault(mySpawn.simple(0, '')); + + before(function () { + return helpers.run(path.join(__dirname, '../../generators/app')) + .withOptions({ skipInstall: true }) + .withPrompts(promptOps) + .toPromise(); + }); + + describe('Copy/create', function () { + it('creates files from generator', function () { + assert.file([ + builder === 'gulp' ? 'Gulpfile.js' : 'Gruntfile.js', + 'package.json', + '.editorconfig', + '.gitignore' + ]); + }); + + it('copies generic files from AppStoreWidgetBoilerPlate', function () { + assert.file([ + '.jshintrc', + 'src/package.xml', + 'test/Test.mpr', + 'xsd/widget.xsd', + 'assets/app_store_banner.png', + 'assets/app_store_icon.png' + ]); + }); + + it(format('copies files from AppStoreWidgetBoilerPlate based on widgetName ({0})', promptOps.widgetName), function () { + assert.file([ + format('src/{0}/{0}.xml', [promptOps.widgetName]), + format('src/{0}/lib/jquery-1.11.2.js', [promptOps.widgetName]), + format('src/{0}/widget/{0}.js', [promptOps.widgetName]), + format('src/{0}/widget/template/{0}.html', [promptOps.widgetName]), + format('src/{0}/widget/ui/{0}.css', [promptOps.widgetName]) + ]); + }); + }); + + describe('Javascript format', function() { + var jsFile = format('src/{0}/widget/{0}.js', [promptOps.widgetName]); + + it('declare', function () { + assert.fileContent(jsFile, format('return declare("{0}.widget.{0}", [ _WidgetBase, _TemplatedMixin ], {', [promptOps.widgetName])); + }); + + it('require', function () { + assert.fileContent(jsFile, format('require(["{0}/widget/{0}"]);', [promptOps.widgetName])); + }); + + }); + + describe('Widget XML format', function() { + var xmlFile = format('src/{0}/{0}.xml', [promptOps.widgetName]); + + it('declare', function () { + assert.fileContent(xmlFile, format('{0}', [promptOps.widgetName])); + }); + }); + + describe('Package XML format', function() { + var xmlFile = 'src/package.xml'; + + it('clientModule', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName, promptOps.version])); + }); + + it('widgetFiles', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName])); + }); + + it('file paths', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName])); + }); + }); + + }); +}; diff --git a/test/lib/testrunner-empty-boilerplate.js b/test/lib/testrunner-empty-boilerplate.js new file mode 100644 index 0000000..1582bfd --- /dev/null +++ b/test/lib/testrunner-empty-boilerplate.js @@ -0,0 +1,157 @@ +/*global it,before,describe*/ +/*jshint -W108,-W069*/ +'use strict'; + +var path = require('path'); +var assert = require('yeoman-assert'); +var helpers = require('yeoman-test'); +var format = require('string-template'); +var mockSpawn = require('mock-spawn'); +var _ = require('lodash'); + +module.exports = function (builder, prompt) { + var promptOps = _.assign(require('./prompt-options.js')(), prompt || {}); + promptOps.widgetName = 'TestWidgetForTHE' + builder; + promptOps.builder = builder; + + var jquery = promptOps.widgetoptions && promptOps.widgetoptions.indexOf('jquery') !== -1, + templates = promptOps.widgetoptions && promptOps.widgetoptions.indexOf('templates') !== -1; + + describe(format('Generator using empty boilerplate with {0}, jquery={1}, templates={2}:\n', builder, jquery, templates), function () { + + var mySpawn = mockSpawn(); + require('child_process').spawn = mySpawn; + + mySpawn.setDefault(mySpawn.simple(0, '')); + + before(function () { + return helpers.run(path.join(__dirname, '../../generators/app')) + .withOptions({ skipInstall: true }) + .withPrompts(promptOps) + .toPromise(); + }); + + describe('Copy/create', function () { + it('creates files from generator', function () { + assert.file([ + builder === 'gulp' ? 'Gulpfile.js' : 'Gruntfile.js', + 'package.json', + '.editorconfig', + '.gitignore', + '.jshintrc' + ]); + }); + + it('copies generic files from AppStoreWidgetBoilerPlate', function () { + assert.file([ + 'src/package.xml', + 'test/Test.mpr', + 'xsd/widget.xsd', + 'assets/app_store_banner.png', + 'assets/app_store_icon.png' + ]); + }); + + it(format('Create files for {0}', promptOps.widgetName), function () { + assert.file([ + format('src/{0}/{0}.xml', [promptOps.widgetName]), + format('src/{0}/widget/{0}.js', [promptOps.widgetName]), + format('src/{0}/widget/ui/{0}.css', [promptOps.widgetName]) + ]); + }); + + it (format('Based on input, jQuery library should{0} be copied', (jquery ? '' : ' NOT')), function () { + if (jquery) { + assert.file([ + format('src/{0}/lib/jquery-1.11.2.js', [promptOps.widgetName]) + ]); + } else { + assert.noFile([ + format('src/{0}/lib/jquery-1.11.2.js', [promptOps.widgetName]) + ]); + } + }); + it (format('Based on input, templates should{0} be copied', (templates ? '' : ' NOT')), function () { + if (templates) { + assert.file([ + format('src/{0}/widget/template/{0}.html', [promptOps.widgetName]) + ]); + } else { + assert.noFile([ + format('src/{0}/widget/template/{0}.html', [promptOps.widgetName]) + ]); + } + }); + }); + + describe('Javascript widget file', function() { + var jsFile = format('src/{0}/widget/{0}.js', [promptOps.widgetName]); + + it('should declare with the name set in the widget generator', function () { + assert.fileContent(jsFile, format('return declare("{0}.widget.{0}"', [promptOps.widgetName])); + }); + + it('will require the widget based on the name set in the widget generator', function () { + assert.fileContent(jsFile, format('require(["{0}/widget/{0}"]);', [promptOps.widgetName])); + }); + + it (format('Based on input, jQuery library should{0} be required in the widget', (jquery ? '' : ' NOT')), function () { + var contents = [ + [jsFile, format('{0}/lib/jquery-1.11.2', [promptOps.widgetName])], + [jsFile, '_jQuery'], + [jsFile, 'var $ = _jQuery.noConflict(true);'] + ]; + if (jquery) { + assert.fileContent(contents); + } else { + assert.noFileContent(contents); + } + }); + it (format('Based on input, templates should{0} be required in the widget', (templates ? '' : ' NOT')), function () { + var contents = [ + [jsFile, format('dojo/text!{0}/widget/template/{0}.html', [promptOps.widgetName])], + [jsFile, 'dijit/_TemplatedMixin'], + [jsFile, 'widgetTemplate'], + [jsFile, 'templateString: widgetTemplate'], + [jsFile, 'widgetBase: null,'] + ]; + if (templates) { + assert.fileContent(contents); + } else { + assert.noFileContent(contents); + } + }); + }); + + describe('Widget XML format', function() { + var xmlFile = format('src/{0}/{0}.xml', [promptOps.widgetName]); + + it('has a widget declaration with proper id', function () { + assert.fileContent(xmlFile, format('{0}', [promptOps.widgetName])); + }); + it('has a dummy key in the widget xml', function () { + assert.fileContent(xmlFile, ''); + }); + }); + + describe('Package XML format', function() { + var xmlFile = 'src/package.xml'; + + it('has a clientModule node with the version number set in the generator', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName, promptOps.version])); + }); + + it('has a widgetFile node that points to the proper widget XML', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName])); + }); + + it('has a file node that points to the widget files', function () { + assert.fileContent(xmlFile, format('', [promptOps.widgetName])); + }); + }); + + }); +}; diff --git a/test/test-app.js b/test/test-app.js deleted file mode 100644 index 66e5cad..0000000 --- a/test/test-app.js +++ /dev/null @@ -1,98 +0,0 @@ -/*global it,before,describe*/ -/*jshint -W108,-W069*/ -'use strict'; - -var path = require('path'); -var assert = require('yeoman-assert'); -var helpers = require('yeoman-test'); -var format = require('string-template'); -var mockSpawn = require('mock-spawn'); - -describe('Generator:', function () { - - var customWidgetName = 'TESTWIDGET'; - var mySpawn = mockSpawn(); - require('child_process').spawn = mySpawn; - - mySpawn.setDefault(mySpawn.simple(0, '')); - - before(function () { - return helpers.run(path.join(__dirname, '../generators/app')) - .withOptions({ skipInstall: true }) - .withPrompts({ widgetName: customWidgetName }) - .toPromise(); - }); - - describe('Copy/create', function () { - it('creates files from generator', function () { - assert.file([ - 'Gruntfile.js', - 'package.json', - '.editorconfig', - '.gitignore' - ]); - }); - - it('copies generic files from AppStoreWidgetBoilerPlate', function () { - assert.file([ - '.jshintrc', - 'src/package.xml', - 'test/Test.mpr', - 'xsd/widget.xsd', - 'assets/app_store_banner.png', - 'assets/app_store_icon.png' - ]); - }); - - it(format('copies files from AppStoreWidgetBoilerPlate based on widgetName ({0})', customWidgetName), function () { - assert.file([ - format('src/{0}/{0}.xml', [customWidgetName]), - format('src/{0}/lib/jquery-1.11.2.js', [customWidgetName]), - format('src/{0}/widget/{0}.js', [customWidgetName]), - format('src/{0}/widget/template/{0}.html', [customWidgetName]), - format('src/{0}/widget/ui/{0}.css', [customWidgetName]) - ]); - }); - }); - - describe('Javascript format', function() { - var jsFile = format('src/{0}/widget/{0}.js', [customWidgetName]); - - it('declare', function () { - assert.fileContent(jsFile, format('return declare("{0}.widget.{0}", [ _WidgetBase, _TemplatedMixin ], {', [customWidgetName])); - }); - - it('require', function () { - assert.fileContent(jsFile, format('require(["{0}/widget/{0}"]);', [customWidgetName])); - }); - - }); - - describe('Widget XML format', function() { - var xmlFile = format('src/{0}/{0}.xml', [customWidgetName]); - - it('declare', function () { - assert.fileContent(xmlFile, format('{0}', [customWidgetName])); - }); - }); - - describe('Package XML format', function() { - var xmlFile = 'src/package.xml'; - - it('clientModule', function () { - assert.fileContent(xmlFile, format('', [customWidgetName])); - }); - - it('widgetFiles', function () { - assert.fileContent(xmlFile, format('', [customWidgetName])); - }); - - it('file paths', function () { - assert.fileContent(xmlFile, format('', [customWidgetName])); - }); - }); - -}); diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..4e06c08 --- /dev/null +++ b/test/test.js @@ -0,0 +1,21 @@ +/*jshint -W108,-W069*/ +require('./lib/testrunner-appstore-boilerplate.js')('gulp'); +require('./lib/testrunner-appstore-boilerplate.js')('grunt'); +require('./tests/not-empty-dir.js'); +require('./tests/upgrade-widget.js'); +require('./lib/testrunner-empty-boilerplate.js')('gulp', { + boilerplate: 'empty', + widgetoptions: ['templates', 'jquery'] +}); +require('./lib/testrunner-empty-boilerplate.js')('gulp', { + boilerplate: 'empty', + widgetoptions: ['templates'] +}); +require('./lib/testrunner-empty-boilerplate.js')('gulp', { + boilerplate: 'empty', + widgetoptions: ['jquery'] +}); +require('./lib/testrunner-empty-boilerplate.js')('gulp', { + boilerplate: 'empty', + widgetoptions: [] +}); diff --git a/test/tests/not-empty-dir.js b/test/tests/not-empty-dir.js new file mode 100644 index 0000000..8656bcf --- /dev/null +++ b/test/tests/not-empty-dir.js @@ -0,0 +1,50 @@ +/*global it,before,describe*/ +/*jshint -W108,-W069*/ +'use strict'; + +var path = require('path'); +var assert = require('yeoman-assert'); +var helpers = require('yeoman-test'); +var format = require('string-template'); +var mockSpawn = require('mock-spawn'); +var fs = require("fs-extra"); +var builder = 'gulp'; + +describe(format('Generator on non-empty folder:\n', builder), function () { + + var mySpawn = mockSpawn(); + require('child_process').spawn = mySpawn; + + mySpawn.setDefault(mySpawn.simple(0, '')); + + before(function () { + return helpers.run(path.join(__dirname, '../../generators/app')) + .inTmpDir(function (dir) { + fs.copySync(path.join(__dirname, '../test.js'), dir + '/test.js'); + }) + .withOptions({ skipInstall: true }) + .toPromise(); + }); + + describe('Break on non empty folder', function () { + it('Leave original files there', function () { + assert.file([ + 'test.js' + ]); + }); + it('Does not copy any files', function () { + assert.noFile([ + builder === 'gulp' ? 'Gulpfile.js' : 'Gruntfile.js', + 'package.json', + '.editorconfig', + '.gitignore', + '.jshintrc', + 'src/package.xml', + 'test/Test.mpr', + 'xsd/widget.xsd', + 'assets/app_store_banner.png', + 'assets/app_store_icon.png' + ]); + }); + }); +}); diff --git a/test/tests/upgrade-widget.js b/test/tests/upgrade-widget.js new file mode 100644 index 0000000..cd5a8bc --- /dev/null +++ b/test/tests/upgrade-widget.js @@ -0,0 +1,48 @@ +/*global it,before,describe*/ +/*jshint -W108,-W069*/ +'use strict'; + +var path = require('path'); +var assert = require('yeoman-assert'); +var helpers = require('yeoman-test'); +var format = require('string-template'); +var mockSpawn = require('mock-spawn'); +var fs = require("fs-extra"); +var builder = 'gulp'; + +describe(format('Generator on non-empty folder, upgrading a widget, using gulp:\n', builder), function () { + + var mySpawn = mockSpawn(); + require('child_process').spawn = mySpawn; + + mySpawn.setDefault(mySpawn.simple(0, '')); + + before(function () { + return helpers.run(path.join(__dirname, '../../generators/app')) + .inTmpDir(function (dir) { + try { + fs.copySync(path.join(__dirname, '../../generators/app/templates/AppStoreWidgetBoilerplate/src'), dir + '/src'); + } catch (err) { + throw new Error(err); + } + }) + .withPrompts({ + upgrade: true, + version: "1.0.0", + builder: "gulp" + }) + .withOptions({ skipInstall: true }) + .toPromise(); + }); + + describe('Break on non empty folder', function () { + it('creates files from generator', function () { + assert.file([ + builder === 'gulp' ? 'Gulpfile.js' : 'Gruntfile.js', + 'package.json', + '.editorconfig', + '.gitignore' + ]); + }); + }); +});