├── .gitattributes ├── widget ├── templates │ ├── .jshintrc │ ├── config.ts │ ├── setting │ │ ├── css │ │ │ ├── _style.css │ │ │ └── _style.scss │ │ ├── nls │ │ │ └── _strings.js │ │ ├── _Setting.html │ │ ├── _Setting_ES2015.js │ │ ├── _Setting_ES5.js │ │ └── _Setting.ts │ ├── images │ │ └── icon.png │ ├── _config.json │ ├── css │ │ ├── _style.css │ │ └── _style.scss │ ├── nls │ │ └── _strings.js │ ├── _Widget.html │ ├── support │ │ └── declareDecorator.ts │ ├── _manifest.json │ ├── _Widget_ES2015.js │ ├── _Widget_ES5.js │ ├── _Widget_2d.ts │ └── _Widget_3d.ts ├── utils.js └── index.js ├── .gitignore ├── AUTHORS.md ├── docs └── images │ ├── widget-in-builder.png │ └── running-the-generators.png ├── .travis.yml ├── CONTRIBUTING.md ├── tslint.json ├── app ├── templates │ ├── tslint │ ├── editorconfig │ ├── babelrc │ └── tsconfig └── index.js ├── .editorconfig ├── .jshintrc ├── RELEASE.md ├── package.json ├── license.txt ├── test ├── test-creation.js └── test-widget.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /widget/templates/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | strict: false 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Tom Wayson (https://github.com/tomwayson) -------------------------------------------------------------------------------- /widget/templates/config.ts: -------------------------------------------------------------------------------- 1 | export default interface IConfig { 2 | serviceUrl: string; 3 | } -------------------------------------------------------------------------------- /widget/templates/setting/css/_style.css: -------------------------------------------------------------------------------- 1 | .<%= baseClass %>-setting input { 2 | margin-left: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /widget/templates/setting/css/_style.scss: -------------------------------------------------------------------------------- 1 | .<%= baseClass %>-setting input { 2 | margin-left: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /widget/templates/setting/nls/_strings.js: -------------------------------------------------------------------------------- 1 | define({ 2 | root: ({ 3 | serviceUrl: 'Set service url:' 4 | }) 5 | }); 6 | -------------------------------------------------------------------------------- /widget/templates/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/HEAD/widget/templates/images/icon.png -------------------------------------------------------------------------------- /docs/images/widget-in-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/HEAD/docs/images/widget-in-builder.png -------------------------------------------------------------------------------- /widget/templates/_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceUrl": "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | os: 4 | - windows 5 | - linux 6 | - mac 7 | node_js: 8 | - 'node' 9 | - 'lts/carbon' -------------------------------------------------------------------------------- /docs/images/running-the-generators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/HEAD/docs/images/running-the-generators.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We welcome contributions from anyone and everyone. Please see the Esri [guidelines for contributing](https://github.com/esri/contributing). -------------------------------------------------------------------------------- /widget/templates/css/_style.css: -------------------------------------------------------------------------------- 1 | .<%= baseClass %> h3 { 2 | font-size: 1.2em; 3 | } 4 | .<%= baseClass %> h3, p { 5 | margin-top: 0; 6 | } 7 | /* add additional custom styles */ -------------------------------------------------------------------------------- /widget/templates/css/_style.scss: -------------------------------------------------------------------------------- 1 | .<%= baseClass %> { 2 | h3 { 3 | font-size: 1.2em; 4 | } 5 | h3, p { 6 | margin-top: 0; 7 | } 8 | } 9 | 10 | /* add additional custom styles */ 11 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "tslint:recommended", 4 | "rules": { 5 | "quotemark": [true, "single", "avoid-escape", "avoid-template"], 6 | "no-console": [false] 7 | }, 8 | "jsRules": { 9 | } 10 | } -------------------------------------------------------------------------------- /app/templates/tslint: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "tslint:recommended", 4 | "rules": { 5 | "quotemark": [true, "single", "avoid-escape", "avoid-template"], 6 | "no-console": [false] 7 | }, 8 | "jsRules": { 9 | } 10 | } -------------------------------------------------------------------------------- /widget/templates/setting/_Setting.html: -------------------------------------------------------------------------------- 1 |
2 | <% if (hasSettingLocale) { %> 3 | 4 | <% } else { %> 5 | 6 | <% } %> 7 |
8 | -------------------------------------------------------------------------------- /widget/templates/nls/_strings.js: -------------------------------------------------------------------------------- 1 | define({ 2 | root: { 3 | _widgetLabel: '<%= widgetTitle %>', 4 | widgetTitle: '<%= widgetTitle %>', 5 | description: '<%= description %>' 6 | } 7 | // add supported locales below: 8 | // , "zh-cn": true 9 | }); 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /widget/templates/_Widget.html: -------------------------------------------------------------------------------- 1 |
2 | <% if (hasLocale) { %> 3 |

${nls.widgetTitle}

4 |

${nls.description}

5 | <% } else { %> 6 |

<%= widgetTitle %>

7 |

<%= description %>

8 | <% } %> 9 |

Service URL: ${config.serviceUrl}

10 |
11 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/templates/babelrc: -------------------------------------------------------------------------------- 1 | { 2 | // blacklisting 'strict' until dojo2 fixes class declarations. see http://dojo-toolkit.33424.n3.nabble.com/A-line-to-use-instead-of-quot-this-inherited-arguments-quot-in-strict-mode-td3999709.html 3 | presets: ['es2015-without-strict', 'stage-0'], 4 | plugins: ['transform-es2015-modules-simple-amd'] 5 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /widget/templates/support/declareDecorator.ts: -------------------------------------------------------------------------------- 1 | import declare = require('dojo/_base/declare'); 2 | 3 | /** 4 | * A decorator that converts a TypeScript class into a declare constructor. 5 | * This allows declare constructors to be defined as classes, which nicely 6 | * hides away the `declare([], {})` boilerplate. 7 | */ 8 | export default function(... mixins: Object[]): Function { 9 | return function(target: Function): Function { 10 | return declare(mixins, target.prototype); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Instructions for running a release 2 | 3 | 1. `npm version _____` (note [semver](https://semver.org/) for deciding if major/minor/patch) 4 | 2. `git push` (or `git push upstream master`) 5 | 3. `git push --tags` (or `git push upstream master --tags`) 6 | 4. Update the GitHub [release documentation](https://github.com/Esri/generator-esri-appbuilder-js/releases). 7 | 5. After the tests run successfully, publish to npm: 8 | 1. `npm logout` 9 | 1. `npm login` 10 | 1. `npm pubish` 11 | 6. Verify that the new version is correctly published to [npm](https://www.npmjs.com/package/generator-esri-appbuilder-js) 12 | -------------------------------------------------------------------------------- /widget/templates/setting/_Setting_ES2015.js: -------------------------------------------------------------------------------- 1 | import declare from 'dojo/_base/declare'; 2 | import BaseWidgetSetting from 'jimu/BaseWidgetSetting'; 3 | 4 | export default declare([BaseWidgetSetting], { 5 | baseClass: '<%= baseClass %>-setting', 6 | 7 | postCreate () { 8 | // the config object is passed in 9 | this.setConfig(this.config); 10 | }, 11 | 12 | setConfig (config) { 13 | this.textNode.value = config.serviceUrl; 14 | }, 15 | 16 | getConfig () { 17 | // WAB will get config object through this method 18 | return { 19 | serviceUrl: this.textNode.value 20 | }; 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /widget/templates/setting/_Setting_ES5.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'dojo/_base/declare', 3 | 'jimu/BaseWidgetSetting' 4 | ], 5 | function(declare, BaseWidgetSetting) { 6 | 7 | return declare([BaseWidgetSetting], { 8 | baseClass: '<%= baseClass %>-setting', 9 | 10 | postCreate: function(){ 11 | //the config object is passed in 12 | this.setConfig(this.config); 13 | }, 14 | 15 | setConfig: function(config){ 16 | this.textNode.value = config.serviceUrl; 17 | }, 18 | 19 | getConfig: function(){ 20 | //WAB will get config object through this method 21 | return { 22 | serviceUrl: this.textNode.value 23 | }; 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /widget/templates/_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= widgetName %>", 3 | "2D": <%= is2d %>, 4 | "3D": <%= is3d %>, 5 | "platform": "<%= platform %>", 6 | "version": "0.0.1", 7 | "wabVersion": "<%= wabVersion %>", 8 | "author": "<%- author %>", 9 | "description": "<%= description %>", 10 | "copyright": "", 11 | "license": "<%= license %>", 12 | "properties": { 13 | "inPanel": <%= inPanel %>, 14 | "hasLocale": <%= hasLocale %>, 15 | "hasStyle": <%= hasStyle %>, 16 | "hasConfig": <%= hasConfig %>, 17 | "hasUIFile": <%= hasUIFile %>, 18 | "hasSettingPage": <%= hasSettingPage %>, 19 | "hasSettingUIFile": <%= hasSettingUIFile %>, 20 | "hasSettingLocale": <%= hasSettingLocale %>, 21 | "hasSettingStyle": <%= hasSettingStyle %>, 22 | "IsController": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/templates/tsconfig: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "compilerOptions": { 4 | "module": "amd", 5 | "moduleResolution": "classic", 6 | "noImplicitAny": true, 7 | "jsx": "react", 8 | "jsxFactory": "tsx", 9 | "target": "es5", 10 | "experimentalDecorators": true, 11 | "preserveConstEnums": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "types": [ "arcgis-js-api", "dojo-typings"], 14 | "rootDir": "./", 15 | "outDir": "dist", 16 | "noImplicitUseStrict": true, 17 | "lib": ["dom", "es5", "scripthost", "es2015.promise", "es2015.iterable"], 18 | "inlineSources": true, 19 | "inlineSourceMap": true, 20 | "esModuleInterop": true 21 | }, 22 | 23 | "include": [ 24 | "themes/**/*.ts", 25 | "widgets/**/*.ts" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-esri-appbuilder-js", 3 | "version": "4.7.0", 4 | "description": "Yeoman generator to help customize the ArcGIS Web AppBuilder", 5 | "license": "Apache-2.0", 6 | "main": "app/index.js", 7 | "repository": "Esri/generator-esri-appbuilder-js", 8 | "author": { 9 | "name": "Tom Wayson", 10 | "email": "twayson@esri.com", 11 | "url": "https://github.com/tomwayson" 12 | }, 13 | "engines": { 14 | "node": ">=8.0.0" 15 | }, 16 | "scripts": { 17 | "test": "mocha --timeout 10000" 18 | }, 19 | "files": [ 20 | "app", 21 | "widget" 22 | ], 23 | "keywords": [ 24 | "yeoman-generator", 25 | "Esri", 26 | "ArcGIS", 27 | "Web", 28 | "AppBuilder" 29 | ], 30 | "dependencies": { 31 | "chalk": "^4.1.0", 32 | "generator-npm-init": "^1.5.1", 33 | "gruntfile-editor": "^1.2.1", 34 | "mkdirp": "^1.0.4", 35 | "underscore.string": "^3.3.5", 36 | "yeoman-generator": "^4.11.0", 37 | "yosay": "^2.0.2" 38 | }, 39 | "devDependencies": { 40 | "mocha": "*", 41 | "yeoman-assert": "^3.1.1", 42 | "yeoman-test": "^3.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /widget/templates/setting/_Setting.ts: -------------------------------------------------------------------------------- 1 | // JIMU (WAB) imports: 2 | 3 | /// 4 | declare var BaseWidgetSetting: any; // there is no ts definition of BaseWidgetSetting (yet!) 5 | 6 | // DeclareDecorator - to enable us to export this module with Dojo's "declare()" syntax so WAB can load it: 7 | import declare from '../support/declareDecorator'; 8 | 9 | import IConfig from '../config'; 10 | 11 | interface ISetting { 12 | config?: IConfig; 13 | } 14 | 15 | @declare(BaseWidgetSetting) 16 | class Setting implements ISetting { 17 | public baseClass: string = '<%= baseClass %>-setting'; 18 | public config: IConfig; 19 | 20 | private textNode: HTMLInputElement; 21 | 22 | public postCreate(args: any): void { 23 | const self: any = this; 24 | self.inherited(arguments); 25 | this.setConfig(this.config); 26 | } 27 | 28 | public setConfig(config: IConfig): void { 29 | this.textNode.value = config.serviceUrl; 30 | } 31 | 32 | public getConfig(): IConfig { 33 | // WAB will get config object through this method 34 | return { 35 | serviceUrl: this.textNode.value, 36 | }; 37 | } 38 | } 39 | 40 | export = Setting; 41 | -------------------------------------------------------------------------------- /widget/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | 4 | var authorToString = function(inStringOrObject) { 5 | if (inStringOrObject) { 6 | if (typeof inStringOrObject === 'object') { 7 | if(inStringOrObject.hasOwnProperty('name')) { 8 | var nameString = inStringOrObject.name; 9 | if (inStringOrObject.hasOwnProperty('email')) { 10 | nameString += ' <' + inStringOrObject.email + '>'; 11 | } 12 | if (inStringOrObject.hasOwnProperty('url')) { 13 | nameString += ' (' + inStringOrObject.url + ')'; 14 | } 15 | return nameString; 16 | } else { 17 | return ''; 18 | } 19 | } else { 20 | return inStringOrObject; 21 | } 22 | } else { 23 | return ''; 24 | } 25 | }; 26 | module.exports.authorToString = authorToString; 27 | 28 | 29 | var getPackageInfo = function(packageProperty) { 30 | try { 31 | var packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); 32 | if(packageJson.hasOwnProperty(packageProperty)) { 33 | return packageJson[packageProperty]; 34 | } else { 35 | // does not have the property we are looking for 36 | return false; 37 | } 38 | } catch (e) { 39 | // no package.json 40 | return false; 41 | } 42 | }; 43 | module.exports.getPackageInfo = getPackageInfo; 44 | -------------------------------------------------------------------------------- /widget/templates/_Widget_ES2015.js: -------------------------------------------------------------------------------- 1 | import declare from 'dojo/_base/declare'; 2 | import BaseWidget from 'jimu/BaseWidget'; 3 | 4 | // To create a widget, you need to derive from BaseWidget. 5 | export default declare([BaseWidget], { 6 | 7 | // Custom widget code goes here 8 | 9 | baseClass: '<%= baseClass %>', 10 | 11 | // add additional properties here 12 | 13 | // methods to communication with app container: 14 | postCreate () { 15 | this.inherited(arguments); 16 | console.log('<%= widgetName %>::postCreate'); 17 | } 18 | // startup() { 19 | // this.inherited(arguments); 20 | // console.log('<%= widgetName %>::startup'); 21 | // }, 22 | // onOpen() { 23 | // console.log('<%= widgetName %>::onOpen'); 24 | // }, 25 | // onClose(){ 26 | // console.log('<%= widgetName %>::onClose'); 27 | // }, 28 | // onMinimize(){ 29 | // console.log('<%= widgetName %>::onMinimize'); 30 | // }, 31 | // onMaximize(){ 32 | // console.log('<%= widgetName %>::onMaximize'); 33 | // }, 34 | // onSignIn(credential){ 35 | // console.log('<%= widgetName %>::onSignIn', credential); 36 | // }, 37 | // onSignOut(){ 38 | // console.log('<%= widgetName %>::onSignOut'); 39 | // } 40 | // onPositionChange(){ 41 | // console.log('<%= widgetName %>::onPositionChange'); 42 | // }, 43 | // resize(){ 44 | // console.log('<%= widgetName %>::resize'); 45 | // } 46 | }); 47 | -------------------------------------------------------------------------------- /widget/templates/_Widget_ES5.js: -------------------------------------------------------------------------------- 1 | define(['dojo/_base/declare', 'jimu/BaseWidget'], 2 | function(declare, BaseWidget) { 3 | //To create a widget, you need to derive from BaseWidget. 4 | return declare([BaseWidget], { 5 | 6 | // Custom widget code goes here 7 | 8 | baseClass: '<%= baseClass %>', 9 | // this property is set by the framework when widget is loaded. 10 | // name: '<%= widgetName %>', 11 | // add additional properties here 12 | 13 | //methods to communication with app container: 14 | postCreate: function() { 15 | this.inherited(arguments); 16 | console.log('<%= widgetName %>::postCreate'); 17 | } 18 | 19 | // startup: function() { 20 | // this.inherited(arguments); 21 | // console.log('<%= widgetName %>::startup'); 22 | // }, 23 | 24 | // onOpen: function(){ 25 | // console.log('<%= widgetName %>::onOpen'); 26 | // }, 27 | 28 | // onClose: function(){ 29 | // console.log('<%= widgetName %>::onClose'); 30 | // }, 31 | 32 | // onMinimize: function(){ 33 | // console.log('<%= widgetName %>::onMinimize'); 34 | // }, 35 | 36 | // onMaximize: function(){ 37 | // console.log('<%= widgetName %>::onMaximize'); 38 | // }, 39 | 40 | // onSignIn: function(credential){ 41 | // console.log('<%= widgetName %>::onSignIn', credential); 42 | // }, 43 | 44 | // onSignOut: function(){ 45 | // console.log('<%= widgetName %>::onSignOut'); 46 | // } 47 | 48 | // onPositionChange: function(){ 49 | // console.log('<%= widgetName %>::onPositionChange'); 50 | // }, 51 | 52 | // resize: function(){ 53 | // console.log('<%= widgetName %>::resize'); 54 | // } 55 | 56 | //methods to communication between widgets: 57 | 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /widget/templates/_Widget_2d.ts: -------------------------------------------------------------------------------- 1 | // jIMU (WAB) imports: 2 | /// 3 | declare var BaseWidget: any; // there is no ts definition of BaseWidget (yet!) 4 | // declareDecorator - to enable us to export this module with Dojo's "declare()" syntax so WAB can load it: 5 | import declare from './support/declareDecorator'; 6 | 7 | // esri imports: 8 | import EsriMap from 'esri/map'; 9 | 10 | // dojo imports: 11 | // import on from 'dojo/on'; 12 | 13 | import IConfig from './config'; 14 | 15 | interface IWidget { 16 | baseClass: string; 17 | config?: IConfig; 18 | } 19 | 20 | @declare(BaseWidget) 21 | class Widget implements IWidget { 22 | public baseClass: string = '<%= baseClass %>'; 23 | public config: IConfig; 24 | 25 | private map: EsriMap; 26 | 27 | private postCreate(args: any): void { 28 | const self: any = this; 29 | self.inherited(arguments); 30 | console.log('<%= widgetName %>::postCreate'); 31 | } 32 | // private startup(): void { 33 | // let self: any = this; 34 | // self.inherited(arguments); 35 | // console.log('<%= widgetName %>::startup'); 36 | // }; 37 | // private onOpen(): void { 38 | // console.log('<%= widgetName %>::onOpen'); 39 | // }; 40 | // private onClose(): void { 41 | // console.log('<%= widgetName %>::onClose'); 42 | // }; 43 | // private onMinimize(): void { 44 | // console.log('<%= widgetName %>::onMinimize'); 45 | // }; 46 | // private onMaximize(): void { 47 | // console.log('<%= widgetName %>::onMaximize'); 48 | // }; 49 | // private onSignIn(credential): void { 50 | // console.log('<%= widgetName %>::onSignIn', credential); 51 | // }; 52 | // private onSignOut(): void { 53 | // console.log('<%= widgetName %>::onSignOut'); 54 | // }; 55 | // private onPositionChange(): void { 56 | // console.log('<%= widgetName %>::onPositionChange'); 57 | // }; 58 | // private resize(): void { 59 | // console.log('<%= widgetName %>::resize'); 60 | // }; 61 | } 62 | 63 | export = Widget; 64 | -------------------------------------------------------------------------------- /widget/templates/_Widget_3d.ts: -------------------------------------------------------------------------------- 1 | // JIMU (WAB) imports: 2 | 3 | /// 4 | declare var BaseWidget: any; // there is no ts definition of BaseWidget (yet!) 5 | 6 | // declareDecorator - to enable us to export this module with Dojo's "declare()" syntax so WAB can load it: 7 | import declare from './support/declareDecorator'; 8 | 9 | // Esri imports: 10 | import SceneView from 'esri/views/SceneView'; 11 | 12 | // dojo imports: (example below) 13 | // import on from 'dojo/on'; 14 | 15 | import IConfig from './config'; 16 | 17 | interface IWidget { 18 | baseClass: string; 19 | config?: IConfig; 20 | } 21 | 22 | @declare(BaseWidget) 23 | class Widget implements IWidget { 24 | public baseClass: string = '<%= baseClass %>'; 25 | public config: IConfig; 26 | 27 | private sceneView: SceneView; 28 | 29 | private postCreate(args: any) { 30 | const self: any = this; 31 | self.inherited(arguments); 32 | console.log('<%= widgetName %>::postCreate'); 33 | } 34 | // private startup(): void { 35 | // let self: any = this; 36 | // self.inherited(arguments); 37 | // console.log('<%= widgetName %>::startup'); 38 | // }; 39 | // private onOpen(): void { 40 | // console.log('<%= widgetName %>::onOpen'); 41 | // }; 42 | // private onClose(): void { 43 | // console.log('<%= widgetName %>::onClose'); 44 | // }; 45 | // private onMinimize(): void { 46 | // console.log('<%= widgetName %>::onMinimize'); 47 | // }; 48 | // private onMaximize(): void { 49 | // console.log('<%= widgetName %>::onMaximize'); 50 | // }; 51 | // private onSignIn(credential): void { 52 | // console.log('<%= widgetName %>::onSignIn', credential); 53 | // }; 54 | // private onSignOut(): void { 55 | // console.log('<%= widgetName %>::onSignOut'); 56 | // }; 57 | // private onPositionChange(): void { 58 | // console.log('<%= widgetName %>::onPositionChange'); 59 | // }; 60 | // private resize(): void { 61 | // console.log('<%= widgetName %>::resize'); 62 | // }; 63 | 64 | } 65 | 66 | export = Widget; 67 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Apache License – 2.0 2 | 3 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 5 | 1. Definitions. 6 | 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 12 | 13 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 14 | 15 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 16 | 17 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 18 | 19 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 20 | 21 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 22 | 23 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 24 | 25 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 26 | 27 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 28 | 29 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 30 | 31 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 32 | 33 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 34 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 35 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 36 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 37 | 38 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 39 | 40 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 41 | 42 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 43 | 44 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 45 | 46 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 47 | 48 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /test/test-creation.js: -------------------------------------------------------------------------------- 1 | /*global describe, before, it */ 2 | 'use strict'; 3 | var path = require('path'); 4 | var assert = require('yeoman-assert'); 5 | var helpers = require('yeoman-test'); 6 | var mkdirp = require('mkdirp'); 7 | var fs = require('fs'); 8 | 9 | var wabRoot = 'wab_root'; 10 | var appDirId = '5'; // arbitrary number since we're creating everything anyway. 11 | var appTitle = 'TestTitle'; // arbitrary title 12 | var appDirPath = path.join(wabRoot, 'server', 'apps', appDirId); 13 | var configFilePath = path.join(appDirPath, 'config.json'); 14 | var configFileContents = '{title:"' + appTitle + '"}'; 15 | 16 | describe('esri-appbuilder-js:app', function () { 17 | before(function (done) { 18 | helpers.run(path.join(__dirname, '../app')) 19 | .withOptions({ skipInstall: true }) 20 | .withPrompts({ 21 | 'wabRoot': wabRoot, 22 | 'appDirId': appDirId, 23 | 'useSass': true, 24 | 'jsVersion': 'ES2015' 25 | }).inTmpDir(function(/*dir*/) { 26 | var done = this.async(); 27 | mkdirp(appDirPath).then(() => { 28 | fs.writeFileSync(configFilePath, configFileContents); 29 | done(); 30 | }); 31 | }) 32 | .on('end', done); 33 | }); 34 | 35 | // TODO: test for existence of widgets folder? 36 | 37 | it('creates expected dotfiles', function () { 38 | var expected = [ 39 | '.editorconfig', 40 | '.babelrc', 41 | '.yo-rc.json' 42 | ]; 43 | assert.file(expected); 44 | }); 45 | 46 | it('the sass setting is stored in config', function() { 47 | assert.fileContent('.yo-rc.json', /"useSass": true/); 48 | }); 49 | 50 | it('creates package.json', function(){ 51 | assert.file('package.json'); 52 | }); 53 | it('sets npm run build', function(){ 54 | assert.fileContent('package.json','"build": "esri-wab-build '); 55 | }); 56 | 57 | describe('when creating gruntfile', function() { 58 | it('sets stemappDir variable', function() { 59 | assert.fileContent('Gruntfile.js', new RegExp('var stemappDir = \'' + path.join(wabRoot, 'client', 'stemapp').replace(/\\/g, '/'))); 60 | }); 61 | it('sets appDir variable', function() { 62 | assert.fileContent('Gruntfile.js', new RegExp('var appDir = \'' + path.join(wabRoot, 'server', 'apps', appDirId).replace(/\\/g, '/'))); 63 | }); 64 | it('sets watch config', function() { 65 | assert.fileContent('Gruntfile.js', new RegExp('watch:')); 66 | }); 67 | it('loads watch task', function() { 68 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-contrib-watch'\);/); 69 | }); 70 | it('sets sync config', function() { 71 | assert.fileContent('Gruntfile.js', new RegExp('sync:')); 72 | }); 73 | it('loads sync task', function() { 74 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sync'\);/); 75 | }); 76 | it('loads sync babel task', function() { 77 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-babel'\);/); 78 | }); 79 | it('registers default task', function() { 80 | assert.fileContent('Gruntfile.js', /grunt.registerTask\('default',/); 81 | }); 82 | it('enables livereload', function() { 83 | assert.fileContent('Gruntfile.js', /livereload: true/); 84 | }); 85 | 86 | 87 | it('sets sass config', function() { 88 | assert.fileContent('Gruntfile.js', new RegExp('sass:')); 89 | }); 90 | it('loads sass task', function() { 91 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sass'\);/); 92 | }); 93 | }); 94 | }); 95 | 96 | describe('esri-appbuilder-js:app no sass', function () { 97 | before(function (done) { 98 | helpers.run(path.join(__dirname, '../app')) 99 | .withOptions({ skipInstall: true }) 100 | .withPrompts({ 101 | 'wabRoot': wabRoot, 102 | 'appDirId': appDirId, 103 | 'useSass': false, 104 | 'jsVersion': 'ES2015' 105 | }).inTmpDir(function(/*dir*/) { 106 | var done = this.async(); 107 | mkdirp(appDirPath).then(() => { 108 | fs.writeFileSync(configFilePath, configFileContents); 109 | done(); 110 | }); 111 | }) 112 | .on('end', done); 113 | }); 114 | 115 | it('creates expected dotfiles', function () { 116 | var expected = [ 117 | '.editorconfig', 118 | '.yo-rc.json' 119 | ]; 120 | assert.file(expected); 121 | }); 122 | 123 | it('the sass setting is stored in config', function() { 124 | assert.fileContent('.yo-rc.json', /"useSass": false/); 125 | }); 126 | 127 | describe('when creating gruntfile', function() { 128 | 129 | it('does not set sass config', function() { 130 | assert.noFileContent('Gruntfile.js', new RegExp('sass:')); 131 | }); 132 | it('does not load sass task', function() { 133 | assert.noFileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sass'\);/); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('esri-appbuilder-js generator - no app', function () { 139 | before(function (done) { 140 | helpers.run(path.join(__dirname, '../app')) 141 | .withOptions({ skipInstall: true }) 142 | .withPrompts({ 143 | 'wabRoot': wabRoot, 144 | 'appDirId': 'None' 145 | }).inTmpDir(function(/*dir*/) { 146 | var done = this.async(); 147 | mkdirp(appDirPath).then(() => { 148 | fs.writeFileSync(configFilePath, configFileContents); 149 | done(); 150 | }); 151 | }) 152 | .on('end', done); 153 | }); 154 | 155 | it('does not set a build script', function(){ 156 | assert.noFileContent('package.json','"build": "esri-wab-build'); 157 | }); 158 | 159 | describe('when creating gruntfile', function() { 160 | it('appDir set to "todo"', function() { 161 | assert.fileContent('Gruntfile.js', new RegExp('var appDir = \'TODO(.*)')); 162 | }); 163 | // TODO - not testing the other parts of the gruntfile here since it's the same as the previous 164 | // case. That common code should be pulled out in the future. 165 | }); 166 | }); 167 | 168 | describe('esri-appbuilder-js:3dapp', function () { 169 | before(function (done) { 170 | helpers.run(path.join(__dirname, '../app')) 171 | .withOptions({ skipInstall: true }) 172 | .withPrompts({ 173 | 'wabRoot': wabRoot, 174 | 'appDirId': appDirId, 175 | 'widgetsType': 'is3d', 176 | 'jsVersion': 'ES2015' 177 | }).inTmpDir(function(/*dir*/) { 178 | var done = this.async(); 179 | mkdirp(appDirPath).then(() => { 180 | fs.writeFileSync(configFilePath, configFileContents); 181 | done(); 182 | }); 183 | }) 184 | .on('end', done); 185 | }); 186 | 187 | // TODO: test for existence of widgets folder? 188 | 189 | it('creates expected dotfiles', function () { 190 | var expected = [ 191 | '.editorconfig', 192 | '.yo-rc.json' 193 | ]; 194 | assert.file(expected); 195 | }); 196 | 197 | it('the 3d choice is stored in config', function() { 198 | assert.fileContent('.yo-rc.json', /"widgetsType": "is3d"/); 199 | }); 200 | 201 | describe('when creating gruntfile', function() { 202 | it('sets stemappDir variable', function() { 203 | assert.fileContent('Gruntfile.js', new RegExp('var stemappDir = \'' + path.join(wabRoot, 'client', 'stemapp3d').replace(/\\/g, '/'))); 204 | }); 205 | it('sets appDir variable', function() { 206 | assert.fileContent('Gruntfile.js', new RegExp('var appDir = \'' + path.join(wabRoot, 'server', 'apps', appDirId).replace(/\\/g, '/'))); 207 | }); 208 | it('sets watch config', function() { 209 | assert.fileContent('Gruntfile.js', new RegExp('watch:')); 210 | }); 211 | it('loads watch task', function() { 212 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-contrib-watch'\);/); 213 | }); 214 | it('sets sync config', function() { 215 | assert.fileContent('Gruntfile.js', new RegExp('sync:')); 216 | }); 217 | it('loads sync task', function() { 218 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sync'\);/); 219 | }); 220 | it('registers default task', function() { 221 | assert.fileContent('Gruntfile.js', /grunt.registerTask\('default',/); 222 | }); 223 | }); 224 | }); 225 | 226 | describe('esri-appbuilder-js generator - 3d no app', function () { 227 | before(function (done) { 228 | helpers.run(path.join(__dirname, '../app')) 229 | .withOptions({ skipInstall: true }) 230 | .withPrompts({ 231 | 'wabRoot': wabRoot, 232 | 'appDirId': 'None', 233 | 'widgetsType': 'is3d', 234 | 'jsVersion': 'ES2015' 235 | }).inTmpDir(function(/*dir*/) { 236 | var done = this.async(); 237 | mkdirp(appDirPath).then(() => { 238 | fs.writeFileSync(configFilePath, configFileContents); 239 | done(); 240 | }); 241 | }) 242 | .on('end', done); 243 | }); 244 | 245 | it('the 3d choice is stored in config', function() { 246 | assert.fileContent('.yo-rc.json', /"widgetsType": "is3d"/); 247 | }); 248 | 249 | it('does not set a build script', function(){ 250 | assert.noFileContent('package.json','"build": "esri-wab-build'); 251 | }); 252 | 253 | describe('when creating gruntfile', function() { 254 | it('appDir set to "todo"', function() { 255 | assert.fileContent('Gruntfile.js', new RegExp('var appDir = \'TODO(.*)')); 256 | }); 257 | // TODO - not testing the other parts of the gruntfile here since it's the same as the previous 258 | // case. That common code should be pulled out in the future. 259 | }); 260 | }); 261 | 262 | 263 | describe('esri-appbuilder-js:app - TypeScript', function () { 264 | before(function (done) { 265 | helpers.run(path.join(__dirname, '../app')) 266 | .withOptions({ skipInstall: true }) 267 | .withPrompts({ 268 | 'wabRoot': wabRoot, 269 | 'appDirId': appDirId, 270 | 'useSass': true, 271 | 'jsVersion': 'TypeScript' 272 | }).inTmpDir(function(/*dir*/) { 273 | var done = this.async(); 274 | mkdirp(appDirPath).then(() => { 275 | fs.writeFileSync(configFilePath, configFileContents); 276 | done(); 277 | }); 278 | }) 279 | .on('end', done); 280 | }); 281 | 282 | // TODO: test for existence of widgets folder? 283 | 284 | it('creates expected dotfiles', function () { 285 | var expected = [ 286 | '.editorconfig', 287 | 'tsconfig.json', 288 | 'tslint.json', 289 | '.yo-rc.json' 290 | ]; 291 | assert.file(expected); 292 | }); 293 | 294 | it('the sass setting is stored in config', function() { 295 | assert.fileContent('.yo-rc.json', /"useSass": true/); 296 | }); 297 | 298 | describe('when creating gruntfile', function() { 299 | it('sets stemappDir variable', function() { 300 | assert.fileContent('Gruntfile.js', new RegExp('var stemappDir = \'' + path.join(wabRoot, 'client', 'stemapp').replace(/\\/g, '/'))); 301 | }); 302 | it('sets appDir variable', function() { 303 | assert.fileContent('Gruntfile.js', new RegExp('var appDir = \'' + path.join(wabRoot, 'server', 'apps', appDirId).replace(/\\/g, '/'))); 304 | }); 305 | it('sets watch config', function() { 306 | assert.fileContent('Gruntfile.js', new RegExp('watch:')); 307 | }); 308 | it('loads watch task', function() { 309 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-contrib-watch'\);/); 310 | }); 311 | it('sets sync config', function() { 312 | assert.fileContent('Gruntfile.js', new RegExp('sync:')); 313 | }); 314 | it('loads sync task', function() { 315 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sync'\);/); 316 | }); 317 | it('loads sync ts task', function() { 318 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-ts'\);/); 319 | }); 320 | it('registers default task', function() { 321 | assert.fileContent('Gruntfile.js', /grunt.registerTask\('default',/); 322 | }); 323 | 324 | 325 | it('sets sass config', function() { 326 | assert.fileContent('Gruntfile.js', new RegExp('sass:')); 327 | }); 328 | it('loads sass task', function() { 329 | assert.fileContent('Gruntfile.js', /grunt.loadNpmTasks\('grunt-sass'\);/); 330 | }); 331 | }); 332 | }); 333 | -------------------------------------------------------------------------------- /widget/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const Generator = require('yeoman-generator'); 4 | const chalk = require('chalk'); 5 | const dasherize = require('underscore.string/dasherize'); 6 | const utils = require('./utils'); 7 | 8 | const fs = require("fs") 9 | 10 | module.exports = class extends Generator { 11 | prompting() { 12 | var done = this.async(); 13 | 14 | console.log(chalk.green('Welcome to the ArcGIS Web AppBuilder widget generator!')); 15 | 16 | var prompts = [{ 17 | name:"widgetPath", 18 | message: "Choose your widget directory: ", 19 | when: function (response) { 20 | return !fs.existsSync("./.yo-rc.json") 21 | }, 22 | validate(answer) { 23 | if (!fs.existsSync(answer)){ 24 | return 'Invalid path. Please ensure this is a valid path to your widget source folder.'; 25 | } else { 26 | return true; 27 | } 28 | } 29 | },{ 30 | type: 'list', 31 | choices: [ 32 | { 33 | value: 'is2d', 34 | name: '2D' 35 | }, 36 | { 37 | value: 'is3d', 38 | name: '3D' 39 | } 40 | ], 41 | name: 'widgetsType', 42 | message: 'Type of widget(s) to be generated:', 43 | when: function (response) { 44 | // only show if we dont have a config to work from' 45 | return !fs.existsSync("./.yo-rc.json") 46 | } 47 | },{ 48 | type: 'confirm', 49 | message: 'Would you like to use SASS for CSS preprocessing?', 50 | name: 'useSass', 51 | when: function (response) { 52 | // only show if we dont have a config to work from' 53 | return !fs.existsSync("./.yo-rc.json") 54 | } 55 | }, { 56 | name: 'jsVersion', 57 | type: 'list', 58 | message: 'Which JavaScript syntax version would you like to develop in?', 59 | choices: ['ES5', 'ES2015', 'TypeScript'], 60 | when: function (response) { 61 | // only show if we dont have a config to work from' 62 | return !fs.existsSync("./.yo-rc.json") 63 | } 64 | },{ 65 | name: 'widgetName', 66 | message: 'Widget Name:', 67 | 'default': 'MyWidget', 68 | // test for valid folder name 69 | validate: function (answer) { 70 | var validFolderNameRegExp = /^[^\\/?%*"":|<>\.\s]+$/; 71 | return validFolderNameRegExp.test(answer); 72 | } 73 | }, { 74 | name: 'widgetTitle', 75 | message: 'Widget Title:', 76 | // default to widget name split on caplital letters 77 | // Ex: MyWidget => My Widget 78 | 'default': function (answers) { 79 | var title; 80 | if (answers && answers.widgetName && answers.widgetName.match) { 81 | title = answers.widgetName.match(/([A-Z]?[^A-Z]*)/g).slice(0, -1).join(' '); 82 | } else { 83 | title = 'My Widget'; 84 | } 85 | return title; 86 | } 87 | }, { 88 | name: 'description', 89 | message: 'Description:', 90 | 'default': 'A custom Web AppBuilder widget.' 91 | }, 92 | { 93 | name: 'baseClass', 94 | message: 'Base Class:', 95 | // default to dasherized widget name 96 | // Ex: MyWidget => my-widget 97 | 'default': function (answers) { 98 | var baseClass; 99 | if (answers && answers.widgetName) { 100 | baseClass = dasherize(answers.widgetName).replace(/^-/, ''); 101 | } else { 102 | baseClass = 'my-widget'; 103 | } 104 | return baseClass; 105 | } 106 | // TODO: validate not empty string? 107 | }, 108 | { 109 | type: 'checkbox', 110 | message: 'Which features would you like to include?', 111 | name: 'features', 112 | choices: [ 113 | { 114 | value: 'inPanel', 115 | name: 'Run inside a panel' 116 | }, 117 | { 118 | value: 'hasLocale', 119 | name: 'Locale (i18n) file' 120 | }, 121 | { 122 | value: 'hasStyle', 123 | name: 'Style (CSS) file' 124 | }, 125 | { 126 | value: 'hasConfig', 127 | name: 'Config (JSON) file' 128 | }, 129 | { 130 | value: 'hasUIFile', 131 | name: 'Template (HTML) file' 132 | } 133 | ], 134 | 'default': ['inPanel', 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile'] 135 | }, 136 | { 137 | when: function (response) { 138 | // only show this step if user answered TRUE to 'hasConfig' 139 | return response.features.indexOf('hasConfig') > -1; 140 | }, 141 | type: 'confirm', 142 | message: 'Would you like a settings page?', 143 | name: 'hasSettingPage' 144 | }, 145 | { 146 | when: function (response) { 147 | // only show this step if user answered TRUE to 'hasSettingPage' 148 | return response.hasSettingPage; 149 | }, 150 | type: 'checkbox', 151 | message: 'Which settings page features would you like to include?', 152 | name: 'settingsFeatures', 153 | choices: [ 154 | { 155 | value: 'hasSettingUIFile', 156 | name: 'Settings template (HTML) file' 157 | }, 158 | { 159 | value: 'hasSettingLocale', 160 | name: 'Settings locale (i18n) file' 161 | }, 162 | { 163 | value: 'hasSettingStyle', 164 | name: 'Settings style (CSS) file' 165 | } 166 | ], 167 | 'default': ['hasSettingUIFile', 'hasSettingLocale', 'hasSettingStyle'] 168 | }]; 169 | 170 | this.prompt(prompts).then(function (props) { 171 | this.widgetName = props.widgetName; 172 | this.widgetTitle = props.widgetTitle; 173 | this.description = props.description; 174 | this.widgetPath = props.widgetPath; 175 | 176 | // properties that we need to get from the package json, if it exists: 177 | this.author = utils.authorToString(utils.getPackageInfo('author')); 178 | this.license = (utils.getPackageInfo('license') !== false ? utils.getPackageInfo('license') : ''); 179 | 180 | // if new path is used pull details from user input, else use config. 181 | if (this.widgetPath) { 182 | this.jsVersion = props.jsVersion 183 | this.useSass = props.useSass 184 | this.widgetsType = props.widgetsType 185 | } else { 186 | this.widgetsType = this.config.get('widgetsType'); 187 | this.useSass = this.config.get('useSass'); 188 | this.jsVersion = this.config.get('jsVersion'); 189 | } 190 | 191 | this.is2d = (this.widgetsType === 'is2d'); 192 | this.is3d = (this.widgetsType === 'is3d'); 193 | this.wabVersion = '2.17'; 194 | 195 | if (this.is3d) { 196 | this.platform = 'HTML3D'; 197 | } else { 198 | this.platform = 'HTML'; 199 | this.is2d = true; 200 | } 201 | 202 | this.baseClass = props.baseClass; 203 | this.inPanel = props.features.indexOf('inPanel') > -1; 204 | this.hasLocale = props.features.indexOf('hasLocale') > -1; 205 | this.hasStyle = props.features.indexOf('hasStyle') > -1; 206 | this.hasConfig = props.features.indexOf('hasConfig') > -1; 207 | this.hasUIFile = props.features.indexOf('hasUIFile') > -1; 208 | // settings 209 | this.hasSettingPage = (props.hasSettingPage === true); 210 | this.hasSettingUIFile = this.hasSettingPage ? (props.settingsFeatures.indexOf('hasSettingUIFile') > -1) : false; 211 | this.hasSettingLocale = this.hasSettingPage ? (props.settingsFeatures.indexOf('hasSettingLocale') > -1) : false; 212 | this.hasSettingStyle = this.hasSettingPage ? (props.settingsFeatures.indexOf('hasSettingStyle') > -1) : false; 213 | this.needsManifestProps = (!this.inPanel || !this.hasLocale); 214 | done(); 215 | }.bind(this)); 216 | } 217 | 218 | writing() { 219 | 220 | // if a new path has been chosen by user, reset the basePath 221 | let basePath = path.join('widgets', this.widgetName); 222 | if (this.widgetPath !== undefined) { 223 | basePath = path.join(this.widgetPath, this.widgetName); 224 | } 225 | 226 | if (this.jsVersion === 'TypeScript') { 227 | var templatePath = '_Widget_2d.ts'; 228 | if (this.is3d) { 229 | templatePath = '_Widget_3d.ts'; 230 | } 231 | this.fs.copyTpl( 232 | this.templatePath(templatePath), 233 | this.destinationPath(path.join(basePath, 'Widget.ts')), 234 | this 235 | ); 236 | 237 | this.fs.copyTpl( 238 | this.templatePath('config.ts'), 239 | this.destinationPath(path.join(basePath, 'config.ts')), 240 | this 241 | ); 242 | 243 | // If we're using TypeScript, we also need the "declareDecorator" file. 244 | this.fs.copyTpl( 245 | this.templatePath('support/declareDecorator.ts'), 246 | this.destinationPath(path.join(basePath, 'support/declareDecorator.ts')), 247 | this 248 | ); 249 | } else { 250 | this.fs.copyTpl( 251 | this.templatePath('_Widget_' + this.jsVersion + '.js'), 252 | this.destinationPath(path.join(basePath, 'Widget.js')), 253 | this 254 | ); 255 | } 256 | 257 | if (this.hasUIFile) { 258 | this.fs.copyTpl( 259 | this.templatePath('_Widget.html'), 260 | this.destinationPath(path.join(basePath, 'Widget.html')), 261 | this 262 | ); 263 | } 264 | if (this.hasConfig) { 265 | this.fs.copyTpl( 266 | this.templatePath('_config.json'), 267 | this.destinationPath(path.join(basePath, 'config.json')), 268 | this 269 | ); 270 | } 271 | if (this.hasStyle) { 272 | if (this.useSass) { 273 | this.fs.copyTpl( 274 | this.templatePath('css/_style.scss'), 275 | this.destinationPath(path.join(basePath, 'css/style.scss')), 276 | this 277 | ); 278 | } else { 279 | this.fs.copyTpl( 280 | this.templatePath('css/_style.css'), 281 | this.destinationPath(path.join(basePath, 'css/style.css')), 282 | this 283 | ); 284 | } 285 | 286 | } 287 | if (this.hasLocale) { 288 | this.fs.copyTpl( 289 | this.templatePath('nls/_strings.js'), 290 | this.destinationPath(path.join(basePath, 'nls/strings.js')), 291 | this 292 | ); 293 | } 294 | this.fs.copy( 295 | this.templatePath('images/icon.png'), 296 | this.destinationPath(path.join(basePath, 'images/icon.png')) 297 | ); 298 | 299 | this.fs.copyTpl( 300 | this.templatePath('_manifest.json'), 301 | this.destinationPath(path.join(basePath, 'manifest.json')), 302 | this 303 | ); 304 | 305 | // Settings: 306 | if (this.hasSettingPage) { 307 | if (this.jsVersion === 'TypeScript') { 308 | this.fs.copyTpl( 309 | this.templatePath('setting/_Setting.ts'), 310 | this.destinationPath(path.join(basePath, 'setting/Setting.ts')), 311 | this 312 | ); 313 | } else { 314 | this.fs.copyTpl( 315 | this.templatePath('setting/_Setting_' + this.jsVersion + '.js'), 316 | this.destinationPath(path.join(basePath, 'setting/Setting.js')), 317 | this 318 | ); 319 | } 320 | if (this.hasSettingUIFile) { 321 | this.fs.copyTpl( 322 | this.templatePath('setting/_Setting.html'), 323 | this.destinationPath(path.join(basePath, 'setting/Setting.html')), 324 | this 325 | ); 326 | } 327 | if (this.hasSettingLocale) { 328 | this.fs.copyTpl( 329 | this.templatePath('setting/nls/_strings.js'), 330 | this.destinationPath(path.join(basePath, 'setting/nls/strings.js')), 331 | this 332 | ); 333 | } 334 | if (this.hasSettingStyle) { 335 | if (this.useSass) { 336 | this.fs.copyTpl( 337 | this.templatePath('setting/css/_style.scss'), 338 | this.destinationPath(path.join(basePath, 'setting/css/style.scss')), 339 | this 340 | ); 341 | } else { 342 | this.fs.copyTpl( 343 | this.templatePath('setting/css/_style.css'), 344 | this.destinationPath(path.join(basePath, 'setting/css/style.css')), 345 | this 346 | ); 347 | } 348 | } 349 | } 350 | } 351 | }; 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Archived**: this project has been archived. For help getting started building Web AppBuilder widgets, see [Widget Development](https://developers.arcgis.com/web-appbuilder/guide/get-started.htm) and [Theme Development](https://developers.arcgis.com/web-appbuilder/guide/concepts.htm) on the [official documentation site](https://developers.arcgis.com/web-appbuilder/). 2 | 3 | # generator-esri-appbuilder-js [![Build Status](https://secure.travis-ci.org/Esri/generator-esri-appbuilder-js.png?branch=master)](https://travis-ci.org/Esri/generator-esri-appbuilder-js) 4 | 5 | > [Yeoman](http://yeoman.io) generator to help customize [the ArcGIS Web AppBuilder](https://developers.arcgis.com/web-appbuilder/). 6 | 7 | ## About 8 | 9 | This generator scaffolds out the boilerplate files that are needed when you are customizing the Web AppBuilder. This includes [generators](#running-the-generators) to set up your project and scaffold out the files needed to create custom widgets. 10 | 11 | ![Screenshot](https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/master/docs/images/running-the-generators.png) 12 | 13 | ## Getting Started 14 | 15 | ### Installation 16 | 17 | The first prerequisite is to have the [grunt-cli](https://gruntjs.com/getting-started) installed. To install this, run: 18 | 19 | ```bash 20 | npm install -g grunt-cli 21 | ``` 22 | 23 | Next you must install [Yeoman](http://yeoman.io/): 24 | 25 | ```bash 26 | $ npm install -g yo 27 | ``` 28 | 29 | Finally install generator-esri-appbuilder-js from npm: 30 | 31 | ```bash 32 | $ npm install -g generator-esri-appbuilder-js 33 | ``` 34 | 35 | ### Running the Generators 36 | 37 | The generators should be run in the root of a working folder for your project. This should be *outside* of the Web AppBuilder's folder structure (i.e. NOT in the stem app or an app that you've already created with the Web AppBuilder). The grunt tasks configured by the generators will handle copying the widget files to the appropriate folders under the Web AppBuilder's install directory. Because of this, the generator will ask you what app to use. If you select `None` or do not have any Web AppBuilder apps in your Web AppBuilder install directory, the grunt file will be created but will not be configured to copy your code to the appropriate app directory. If you create an app _after_ running the Yeoman generator, you can either go to the Gruntfile and make manual edits (you'll see details in there), or you can re-run this generator and it will offer to overwrite your Gruntfile. 38 | 39 | #### App (Default) Generator 40 | 41 | The app generator installs and configures the [grunt tasks](#running-the-grunt-tasks) and other project files and ensures that required subfolders (like widgets) exist. 42 | 43 | 1. Navigate into the root folder of your project 44 | 2. Run the generator with `yo esri-appbuilder-js` 45 | 3. Answer the man's questions! 46 | 47 | |Prompt|Description|Default| 48 | |------|-----------|-------| 49 | |Type of widget(s) to be generated|Whether you want to build 2D or 3D widgets|2D| 50 | |Web AppBuilder install root|The root folder where you installed (unzipped) the Web AppBuilder Developer Edition|[USER_HOME_FOLDER]/WebAppBuilderForArcGIS| 51 | |Web AppBuilder application|The name of the application you would like the grunt task to sync your code with|None| 52 | |Would you like to use SASS for CSS preprocessing?|If you choose yes, you can utilize features from [SASS](http://sass-lang.com/) like nesting, variables, etc.|Yes| 53 | |Which JavaScript syntax version would you like to develop in?|Will widget and settings JavaScript files use ES5, [ES2015](https://babeljs.io/learn-es2015/), or [TypeScript](https://www.typescriptlang.org/)?|ES5| 54 | 55 | 56 | #### Widget Generator 57 | 58 | Scaffolds out the files needed to create a new custom widget. 59 | 60 | 1. Navigate into the root folder of your project 61 | 2. Run the generator with `yo esri-appbuilder-js:widget` 62 | 3. Answer the man's questions! 63 | 64 | |Prompt|Description|Default| 65 | |------|-----------|-------| 66 | |Widget Name|Folder name for output files and widget identifier|MyWidget| 67 | |Widget Title|Name users see in widget selector and panel title bar|My Widget| 68 | |Description|What does this widget do? (optional)|A custom Web AppBuilder widget| 69 | |Base Class|The widget's base class|my-widget| 70 | |Run inside a panel|Will your widget run inside a panel?|Yes| 71 | |Locale (i18n) file|Will your widget require a locale file?|Yes| 72 | |Style (CSS) file|Will your widget require a style file?|Yes| 73 | |Config (JSON) file|Will your widget require a configuration file?|Yes| 74 | |Template (HTML) file|Will your widget require a template file?|Yes| 75 | |Would you like a settings page?|Will your widget have a settings page?|Yes 76 | |Settings template (HTML) file|Will your settings page require a template file?|Yes| 77 | |Settings locale (i18n) file|Will your settings page require a locale file?|Yes| 78 | |Settings style (CSS) file|Will your settings page require a style file?|Yes| 79 | 80 | Taking the default values for the prompts will generate the following output under the `widgets` folder (note: if you choose TypeScript style, there will be `.ts` files instead of JS): 81 | 82 | ``` 83 | MyWidget 84 | │ config.json 85 | │ manifest.json 86 | │ Widget.html 87 | │ Widget.js 88 | │ 89 | ├───css 90 | │ style.css (or style.scss) 91 | │ 92 | ├───images 93 | │ icon.png 94 | │ 95 | ├───nls 96 | │ strings.js 97 | │ 98 | └───setting 99 | | Setting.js 100 | | Setting.html 101 | ├───nls 102 | | strings.js 103 | └───css 104 | style.css 105 | ``` 106 | 107 | After you [copy the widget files to the Web AppBuilder's stemapp](#copying-widget-files), the next time you run the Web AppBuilder, you will see something like the following on the widgets panel: 108 | 109 | ![Widget in the Builder](https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/master/docs/images/widget-in-builder.png) 110 | 111 | ##### Optional Settings 112 | Users are also able to scaffold out widgets without creating an application base. When running `yo esri-appbuilder-js:widget` from a root without an existing .yo-rc.json file, these additional questions will be asked prior to the questions above. 113 | 114 | |Prompt|Description|Default| 115 | |------|-----------|-------| 116 | |Choose your widget directory| The location you want your widgt to be built. E.g `./` for root| None| 117 | |Type of widget(s) to be generated|Whether you want to build 2D or 3D widgets|2D| 118 | |Would you like to use SASS for CSS preprocessing?|If you choose yes, you can utilize features from [SASS](http://sass-lang.com/) like nesting, variables, etc.|Yes| 119 | |Which JavaScript syntax version would you like to develop in?|Will widget and settings JavaScript files use ES5, [ES2015](https://babeljs.io/learn-es2015/), or [TypeScript](https://www.typescriptlang.org/)?|ES5| 120 | 121 | 122 | ## Copying Widget Files 123 | 124 | In order for the widgets that you're working on to be available in the Web AppBuilder you will need to copy their files to the appropriate folder under the Web AppBuilder's install root. 125 | 126 | ### Copying the Files Manually 127 | 128 | For example, let's say you've installed the Web AppBuilder in `c:\WebAppBuilderForArcGIS`, then you'll need to copy widget files to the following folder under the stem app: 129 | 130 | ``` 131 | c:\WebAppBuilderForArcGIS\client\stemapp\widgets 132 | ``` 133 | 134 | Also, you'll likely want to copy widget files to any applications that you've created that use them: 135 | 136 | ``` 137 | c:\WebAppBuilderForArcGIS\server\apps\[appId]\widgets 138 | ``` 139 | 140 | Unless you're using the grunt tasks, you'll need to re-copy the files each time you make changes to the files. 141 | 142 | ### Running the Grunt Tasks 143 | 144 | The easiest way to keep your widget files in sync with the Web AppBuilder is to run the grunt tasks. After running the generators, you can run the default grunt task following at the project root: 145 | 146 | ``` 147 | grunt 148 | ``` 149 | 150 | This will copy over any files that haven't already been copied over, and then start watching all files under the widgets folder for changes and re-copy the files to the Web AppBuilder's folders. 151 | 152 | ### Livereload 153 | 154 | The generator enables `Livereload` by default in the Gruntfile that it creates. This means the Grunt watch task will run at the default livereload port so you can use a browser extension to have the page reload when you save your source widget files. See [grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch#optionslivereload) for advanced customization. Extensions are available in all major browsers ([Example for Google Chrome](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en)) 155 | 156 | ### Linting Your Code 157 | 158 | The app generator does not scaffold out any linting files. We recommend that you use some form of linting, either [jshint][jshint], [semistandard][semistandard], or [eslint][eslint]. An example of using semistandard is below: 159 | 160 | 1) Install semistandard: 161 | ```bash 162 | npm install -g semistandard 163 | ``` 164 | 165 | 2) Below is a sample configuration you can add to your package.json: 166 | ```json 167 | "semistandard": { 168 | "globals": [ 169 | "define" 170 | ], 171 | "ignore": [ 172 | "Gruntfile.js", 173 | "**/dist/**/*.js" 174 | ] 175 | } 176 | ``` 177 | 178 | 3) Validate your code: 179 | ```bash 180 | semistandard 181 | ``` 182 | 183 | See the [semistandard docs][semistandard] for more information. 184 | 185 | ### TypeScript 186 | 187 | If you choose `TypeScript` as the answer to the `Which JavaScript syntax version would you like to develop in?`, the TypeScript compiler will be used to build your Widget TypeScript file into an AMD Widget JS file that Web AppBuilder is expecting. There are a few considerations to understand when doing this. 188 | 189 | #### Widgets in Template 190 | 191 | If you add templated widgets to your html (ex: `
accordion pane #1
`), you both need to mix in the template: 192 | 193 | ``` 194 | @declare(BaseWidget, _WidgetsInTemplateMixin) 195 | class Widget implements IWidget { 196 | ``` 197 | 198 | AND call the startup of _WidgetsInTemplateMixin 199 | 200 | ``` 201 | public startup(args: any): void { 202 | BaseWidget.prototype.startup.call(this, args); 203 | _WidgetsInTemplateMixin.prototype.startup.call(this, args); 204 | ``` 205 | 206 | See [here](https://github.com/Esri/generator-esri-appbuilder-js/issues/154) for more information on this pattern. 207 | 208 | ## Building for Production 209 | 210 | When you're ready to publish your application in a user facing environment, improve performance by running: 211 | 212 | ````npm run build```` 213 | 214 | The resulting package can be found in ````dist/buildOut/app.zip```` 215 | 216 | This will repackage and compress your application with only the neccessary elements. We have found this to result in massive performance improvements. 217 | 218 | NOTE: This feature is only available for 2D applications with a chosen Web AppBuilder application. 219 | 220 | ## System Requirements 221 | 222 | We aim to support LTS versions of Node.js. Right now that means Node.js v8.0 and heigher. 223 | 224 | ## Issues 225 | 226 | Find a bug or want to request a new feature? Please let us know by [submitting an issue](https://github.com/Esri/generator-esri-appbuilder-js/issues). 227 | 228 | ## Contributing 229 | 230 | We welcome contributions from anyone and everyone. Please see Esri's [guidelines for contributing](https://github.com/esri/contributing). 231 | 232 | ## Credit 233 | 234 | This generator was inspired by [@steveoh](https://github.com/steveoh) and [@stdavis](https://github.com/stdavis)'s [generator-dojo-widget](https://github.com/steveoh/generator-dojo-widget) as well as [@dbouwman](https://github.com/dbouwman)'s [generator-bootmap](https://github.com/dbouwman/generator-bootmap). 235 | 236 | ## Licensing 237 | 238 | Licensed under the Apache License, Version 2.0 (the "License"); 239 | you may not use this file except in compliance with the License. 240 | You may obtain a copy of the License at 241 | 242 | http://www.apache.org/licenses/LICENSE-2.0 243 | 244 | Unless required by applicable law or agreed to in writing, software 245 | distributed under the License is distributed on an "AS IS" BASIS, 246 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 247 | See the License for the specific language governing permissions and 248 | limitations under the License. 249 | 250 | A copy of the license is available in the repository's [license.txt](https://raw.githubusercontent.com/Esri/generator-esri-appbuilder-js/master/license.txt) file. 251 | 252 | [semistandard]: https://www.npmjs.com/package/semistandard 253 | [eslint]: https://www.npmjs.com/package/eslint 254 | [jshint]: https://www.npmjs.com/package/jshint 255 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const Generator = require('yeoman-generator'); 4 | const yosay = require('yosay'); 5 | const chalk = require('chalk'); 6 | var os = require('os'); 7 | const homedir = os.homedir(); 8 | const isWin = os.platform() === 'win32'; 9 | const fs = require('fs'); 10 | const mkdirp = require('mkdirp'); 11 | const GruntfileEditor = require('gruntfile-editor'); 12 | 13 | function getDirectories(srcpath) { 14 | return fs.readdirSync(srcpath).filter(function(file) { 15 | return fs.statSync(path.join(srcpath, file)).isDirectory() && file !== 'zips'; 16 | }); 17 | } 18 | 19 | module.exports = class extends Generator { 20 | initializing() { 21 | this.gruntfile = new GruntfileEditor(); 22 | } 23 | 24 | prompting() { 25 | var done = this.async(); 26 | var self = this; 27 | 28 | // Have Yeoman greet the user. 29 | this.log(yosay('Welcome to the ArcGIS Web AppBuilder generator!')); 30 | this.log(chalk.yellow('These generators should be run in the root folder of your project.')); 31 | 32 | var prompts = [{ 33 | type: 'list', 34 | choices: [ 35 | { 36 | value: 'is2d', 37 | name: '2D' 38 | }, 39 | { 40 | value: 'is3d', 41 | name: '3D' 42 | } 43 | ], 44 | name: 'widgetsType', 45 | message: 'Type of widget(s) to be generated:' 46 | }, { 47 | name: 'wabRoot', 48 | message: 'Web AppBuilder install root:', 49 | 'default': function(currentAnswers) { 50 | return path.join(homedir, 'WebAppBuilderForArcGIS'); 51 | }, 52 | validate: function(wabPath) { 53 | // make sure input directory and the following paths exist: 54 | // * server 55 | // * client 56 | var paths = [wabPath, path.join(wabPath, 'server'), path.join(wabPath, 'client')]; 57 | try { 58 | paths.forEach(function(path) { 59 | fs.accessSync(path, fs.F_OK); 60 | }); 61 | return true; 62 | } catch (e) { 63 | // It isn't accessible 64 | return 'Invalid path. Please ensure this is a valid path to your WAB root.'; 65 | } 66 | } 67 | }, { 68 | when: function(currentAnswers) { 69 | try { 70 | var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps'); 71 | var appsDirectories = getDirectories(appsPath); 72 | if (appsDirectories.length > 0) { 73 | return true; 74 | } else { 75 | this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.')); 76 | } 77 | } catch (e) { 78 | this.log(chalk.red('You do not have any WAB apps setup yet. After you create an app, please see the Gruntfile for a todo, or run this generator again.')); 79 | } 80 | 81 | }.bind(this), 82 | name: 'appDirId', 83 | type: 'list', 84 | message: 'Web AppBuilder application:', 85 | choices: function(currentAnswers) { 86 | // Always include option for "None" 87 | var retArray = [{ 88 | name: 'None', 89 | value: 'None', 90 | short: 'N' 91 | }]; 92 | var appsPath = path.join(currentAnswers.wabRoot, 'server', 'apps'); 93 | var appsDirectories = getDirectories(appsPath); 94 | appsDirectories.forEach(function(appDirectory) { 95 | try { 96 | // get the config file, convert to JSON, and read the title property 97 | var configPath = path.join(currentAnswers.wabRoot, 'server', 'apps', appDirectory, 'config.json'); 98 | var configJson = JSON.parse(fs.readFileSync(configPath, 'utf8')); 99 | if (configJson.hasOwnProperty('title') && configJson.title !== '') { 100 | retArray.push({ 101 | name: configJson.title, 102 | value: appDirectory, 103 | short: appDirectory 104 | }); 105 | } else { 106 | // does not have title property or is empty. Use the app folder name (number) as the name. 107 | retArray.push({ 108 | name: appDirectory, 109 | value: appDirectory, 110 | short: appDirectory 111 | }); 112 | } 113 | } catch (e) { 114 | // Cannot open the config file. Just use the app folder name (number) as the name 115 | retArray.push({ 116 | name: appDirectory, 117 | value: appDirectory, 118 | short: appDirectory 119 | }); 120 | } 121 | }); 122 | return retArray; 123 | } 124 | }, { 125 | type: 'confirm', 126 | message: 'Would you like to use SASS for CSS preprocessing?', 127 | name: 'useSass' 128 | }, { 129 | name: 'jsVersion', 130 | type: 'list', 131 | message: 'Which JavaScript syntax version would you like to develop in?', 132 | choices: ['ES5', 'ES2015', 'TypeScript'] 133 | }]; 134 | 135 | this.prompt(prompts).then(function(props) { 136 | this.wabRoot = props.wabRoot; 137 | this.widgetsType = props.widgetsType; 138 | this.useSass = props.useSass; 139 | this.jsVersion = props.jsVersion; 140 | if (props.appDirId && props.appDirId !== 'None') { 141 | this.appDirId = props.appDirId; 142 | } else { 143 | this.appDirId = false; 144 | } 145 | done(); 146 | }.bind(this)); 147 | } 148 | 149 | writing() { 150 | 151 | mkdirp('widgets'); 152 | this.config.set('widgetsType', this.widgetsType); 153 | this.config.set('useSass', this.useSass); 154 | this.config.set('jsVersion', this.jsVersion); 155 | 156 | // gruntConfig: 157 | 158 | 159 | // Setting up the stemappDir and appDir Gruntfile variables: 160 | var stemappDir; 161 | if (this.widgetsType === 'is3d') { 162 | stemappDir = path.join(this.wabRoot, 'client', 'stemapp3d'); 163 | } else { 164 | stemappDir = path.join(this.wabRoot, 'client', 'stemapp'); 165 | } 166 | this.appDir = false; 167 | if (this.appDirId) { 168 | this.appDir = path.join(this.wabRoot, 'server', 'apps', this.appDirId); 169 | } 170 | if (isWin) { 171 | // this hack is needed to ensure paths are not escaped when injected into Gruntfile 172 | stemappDir = stemappDir.replace(/\\/g, '/'); 173 | if (this.appDir) { 174 | this.appDir = this.appDir.replace(/\\/g, '/'); 175 | } 176 | } 177 | this.gruntfile.insertVariable('stemappDir', '"' + stemappDir + '"'); 178 | if (this.appDir) { 179 | this.gruntfile.insertVariable('appDir', '"' + this.appDir + '"'); 180 | } else { 181 | this.gruntfile.insertVariable('appDir', '"TODO - AFTER CREATING AN APP, PLEASE PUT PATH HERE AND INSERT ENTRY IN SYNC.MAIN.FILES BELOW."'); 182 | } 183 | 184 | 185 | // SYNC CONFIG 186 | var syncConfig = '{ main: { verbose: true, files: ['; 187 | var filesPrefix = '{cwd: \'dist/\', src: \'**\', dest: '; 188 | syncConfig = syncConfig + filesPrefix + 'stemappDir }'; 189 | if (this.appDir) { 190 | syncConfig = syncConfig + ',' + filesPrefix + 'appDir }'; 191 | } 192 | syncConfig = syncConfig + ']'; 193 | syncConfig = syncConfig + '} }'; 194 | 195 | this.gruntfile.insertConfig('sync', syncConfig); 196 | 197 | if(this.jsVersion === 'TypeScript') { 198 | // TS CONFIG 199 | var tsConfig = { 200 | default: { 201 | tsconfig: { 202 | passThrough: true 203 | } 204 | } 205 | }; 206 | this.gruntfile.insertConfig('ts', JSON.stringify(tsConfig)); 207 | } else { 208 | // BABEL CONFIG 209 | var babelConfig = { 210 | options: { 211 | sourceMap: true 212 | }, 213 | main: { 214 | files: [{ 215 | expand: true, 216 | src: [ 217 | 'widgets/*.js', 218 | 'widgets/**/*.js', 219 | 'widgets/**/**/*.js', 220 | 'widgets/!**/**/nls/*.js', 221 | 'themes/*.js', 222 | 'themes/**/*.js', 223 | 'themes/**/**/*.js', 224 | 'themes/!**/**/nls/*.js' 225 | ], 226 | dest: 'dist/' 227 | }] 228 | } 229 | }; 230 | this.gruntfile.insertConfig('babel', JSON.stringify(babelConfig)); 231 | } 232 | 233 | // WATCH CONFIG 234 | this.gruntfile.insertConfig('watch', `{ 235 | main: { 236 | files: ['widgets/**', 'themes/**'], 237 | tasks: ['clean', ${(this.useSass ? '\'sass\', ' : '')}${(this.jsVersion === 'TypeScript' ? '\'ts\', ' : '\'babel\', ')} 'copy', 'sync'], 238 | options: { 239 | spawn: false, 240 | atBegin: true, 241 | livereload: true 242 | } 243 | } 244 | }`); 245 | 246 | // COPY CONFIG 247 | this.gruntfile.insertConfig('copy', JSON.stringify({ 248 | main: { 249 | src: [ 250 | 'widgets/**/**.html', 251 | 'widgets/**/**.json', 252 | 'widgets/**/**.css', 253 | 'widgets/**/images/**', 254 | 'widgets/**/nls/**', 255 | 'themes/**/**.html', 256 | 'themes/**/**.json', 257 | 'themes/**/**.css', 258 | 'themes/**/images/**', 259 | 'themes/**/nls/**', 260 | 'themes/**/layouts/**/*.*' 261 | ], 262 | dest: 'dist/', 263 | expand: true 264 | } 265 | })); 266 | 267 | // CLEAN CONFIG 268 | this.gruntfile.insertConfig('clean', JSON.stringify({ 269 | dist: { 270 | src: 'dist/*' 271 | } 272 | })); 273 | 274 | // SASS CONFIG 275 | if(this.useSass) { 276 | 277 | // must require in the "sass" variable so it can be used on the "implementation" line below: 278 | this.gruntfile.prependJavaScript('const sass = require(\'node-sass\');'); 279 | 280 | this.gruntfile.insertConfig('sass', `{ 281 | dist: { 282 | options: { 283 | implementation: sass, 284 | sourceMap: true, 285 | }, 286 | 287 | files: [{ 288 | expand: true, 289 | src: ['widgets/**/*.scss'], 290 | rename: function(dest, src) { 291 | return src.replace('scss', 'css') 292 | } 293 | }] 294 | } 295 | }`); 296 | this.gruntfile.loadNpmTasks('node-sass'); 297 | this.gruntfile.loadNpmTasks('grunt-sass'); 298 | } 299 | 300 | 301 | // load tasks 302 | if(this.jsVersion === 'TypeScript') { 303 | this.gruntfile.loadNpmTasks('grunt-ts'); 304 | } else { 305 | this.gruntfile.loadNpmTasks('grunt-babel'); 306 | } 307 | this.gruntfile.loadNpmTasks('grunt-contrib-clean'); 308 | this.gruntfile.loadNpmTasks('grunt-contrib-copy'); 309 | this.gruntfile.loadNpmTasks('grunt-contrib-watch'); 310 | this.gruntfile.loadNpmTasks('grunt-sync'); 311 | 312 | // register tasks 313 | this.gruntfile.registerTask('default', ['watch']); 314 | 315 | // projectFiles: 316 | 317 | this.fs.copyTpl( 318 | this.templatePath('editorconfig'), 319 | this.destinationPath('.editorconfig') 320 | ); 321 | 322 | if(this.jsVersion === 'TypeScript') { 323 | this.fs.copyTpl( 324 | this.templatePath('tsconfig'), 325 | this.destinationPath('tsconfig.json') 326 | ); 327 | this.fs.copyTpl( 328 | this.templatePath('tslint'), 329 | this.destinationPath('tslint.json') 330 | ); 331 | } else { 332 | this.fs.copyTpl( 333 | this.templatePath('babelrc'), 334 | this.destinationPath('.babelrc') 335 | ); 336 | } 337 | const buildObj = { 338 | "skip-test": true, 339 | "skip-main": true, 340 | } 341 | 342 | if (this.appDir && this.widgetsType !== 'is3d'){ 343 | buildObj.scripts = { 344 | "build": `esri-wab-build ${this.appDir}` 345 | }; 346 | } 347 | 348 | this.composeWith(require.resolve('generator-npm-init/app'),buildObj); 349 | 350 | fs.writeFileSync('Gruntfile.js', this.gruntfile.toString()); 351 | } 352 | 353 | install() { 354 | if (this.options['skip-install']) { 355 | return; 356 | } 357 | 358 | // we install different sets of packages depending on TypeScript or not: 359 | var dependencies = [ 360 | 'grunt@^1.0.3', 361 | 'grunt-contrib-clean@^2.0.0', 362 | 'grunt-contrib-copy@^1.0.0', 363 | 'node-sass', 364 | 'grunt-sass@^3.0.1', 365 | 'grunt-sync@^0.8.0', 366 | 'grunt-contrib-watch@^1.1.0', 367 | 'esri-wab-build@^1.0.1' 368 | ]; 369 | 370 | if(this.jsVersion === 'TypeScript') { 371 | dependencies = dependencies.concat([ 372 | 'dojo-typings@^1.11.9', 373 | 'grunt-contrib-connect', 374 | 'grunt-ts@^6.0.0-beta.22', 375 | 'typescript@^3.8.3' 376 | ]); 377 | // 3D vs 2D we need to install a different declarations file: 378 | if (this.widgetsType === 'is3d') { 379 | dependencies.push('@types/arcgis-js-api@4.16.0'); 380 | } else { 381 | dependencies.push('@types/arcgis-js-api@3.33.0'); 382 | } 383 | } else { 384 | dependencies = dependencies.concat([ 385 | 'babel-plugin-transform-es2015-modules-simple-amd', 386 | 'babel-preset-es2015-without-strict', 387 | 'babel-preset-stage-0', 388 | 'grunt-babel@~7.0.0', 389 | 'babel-core@~6.26.3' 390 | ]); 391 | } 392 | 393 | this.npmInstall(dependencies, { 394 | 'saveDev': true 395 | }); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /test/test-widget.js: -------------------------------------------------------------------------------- 1 | /*global describe, before, beforeEach, it */ 2 | 'use strict'; 3 | var path = require('path'); 4 | var assert = require('yeoman-assert'); 5 | var helpers = require('yeoman-test'); 6 | var fs = require('fs'); 7 | 8 | var generatorPath = path.join(__dirname, '../widget'); 9 | var testAuthorName = 'Barney Rubble'; 10 | var testAuthorEmail = 'b@rubble.com'; 11 | var testAuthorUrl = 'http://barnyrubble.tumblr.com'; 12 | var testLicense = 'Apache-2.0'; 13 | 14 | describe('esri-appbuilder-js:widget subgenerator', function () { 15 | 16 | describe('when creating an inPanel widget', function() { 17 | 18 | before(function(done) { 19 | helpers.run(generatorPath).withPrompts({ 20 | widgetName: 'TestWidget', 21 | widgetTitle: 'Test Widget', 22 | description: 'A test widget.', 23 | path: 'widgets', 24 | baseClass: 'test-widget', 25 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile' ], 26 | jsVersion: 'ES2015' 27 | }).withLocalConfig({ 28 | jsVersion: 'ES2015' 29 | }) 30 | .on('end', done); 31 | }); 32 | 33 | it('creates expected files', function (/*done*/) { 34 | var expected = [ 35 | // add files you expect to exist here. 36 | 'widgets/TestWidget/Widget.js', 37 | 'widgets/TestWidget/Widget.html', 38 | 'widgets/TestWidget/config.json', 39 | 'widgets/TestWidget/nls/strings.js', 40 | 'widgets/TestWidget/css/style.css', 41 | 'widgets/TestWidget/images/icon.png', 42 | 'widgets/TestWidget/manifest.json' 43 | // TODO: settings 44 | ]; 45 | // TODO: replace w/ assertFileContent(pairs)? 46 | // see: http://yeoman.github.io/generator/assert.html 47 | assert.file(expected); 48 | }); 49 | 50 | it('should set inPanel to true in manifest', function() { 51 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 52 | }); 53 | 54 | it('sets manifest hasLocale to true in manifest', function() { 55 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": true/); 56 | }); 57 | 58 | it('sets manifest hasConfig to true in manifest', function() { 59 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": true/); 60 | }); 61 | 62 | it('sets manifest hasStyle to true in manifest', function() { 63 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": true/); 64 | }); 65 | 66 | it('sets manifest hasUIFile to true in manifest', function() { 67 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": true/); 68 | }); 69 | 70 | it('has baseClass in widget', function() { 71 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 72 | }); 73 | 74 | it('has baseClass in css', function() { 75 | assert.fileContent('widgets/TestWidget/css/style.css', /\.test-widget/); 76 | }); 77 | 78 | it('references nls in template', function() { 79 | assert.fileContent('widgets/TestWidget/Widget.html', /\$\{nls\./); 80 | }); 81 | 82 | it('has title/description in nls', function() { 83 | assert.fileContent('widgets/TestWidget/nls/strings.js', /widgetTitle: 'Test Widget'/); 84 | assert.fileContent('widgets/TestWidget/nls/strings.js', /description: 'A test widget\.'/); 85 | }); 86 | 87 | it('should set Label to _widgetTitle in NLS strings file', function() { 88 | assert.fileContent('widgets/TestWidget/nls/strings.js', /_widgetLabel: 'Test Widget'/); 89 | }); 90 | 91 | }); 92 | 93 | describe('when creating a non-inPanel widget', function() { 94 | 95 | before(function(done) { 96 | helpers.run(generatorPath).withPrompts({ 97 | widgetName: 'TestWidget', 98 | widgetTitle: 'Test Widget', 99 | description: 'A test widget.', 100 | path: 'widgets', 101 | baseClass: 'test-widget', 102 | features: [ 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile' ] 103 | }).withLocalConfig({ 104 | jsVersion: 'ES2015' 105 | }) 106 | .on('end', done); 107 | }); 108 | 109 | it('creates expected files', function (/*done*/) { 110 | var expected = [ 111 | // add files you expect to exist here. 112 | 'widgets/TestWidget/Widget.js', 113 | 'widgets/TestWidget/Widget.html', 114 | 'widgets/TestWidget/config.json', 115 | 'widgets/TestWidget/nls/strings.js', 116 | 'widgets/TestWidget/css/style.css', 117 | 'widgets/TestWidget/images/icon.png', 118 | 'widgets/TestWidget/manifest.json' 119 | // TODO: settings 120 | ]; 121 | assert.file(expected); 122 | }); 123 | 124 | it('sets inPanel to false in manifest', function() { 125 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": false/); 126 | }); 127 | 128 | it('sets manifest hasLocale to true in manifest', function() { 129 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": true/); 130 | }); 131 | 132 | it('sets manifest hasConfig to true in manifest', function() { 133 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": true/); 134 | }); 135 | 136 | it('sets manifest hasStyle to true in manifest', function() { 137 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": true/); 138 | }); 139 | 140 | it('sets manifest hasUIFile to true in manifest', function() { 141 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": true/); 142 | }); 143 | 144 | it('has baseClass in widget', function() { 145 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 146 | }); 147 | 148 | it('has baseClass in css', function() { 149 | assert.fileContent('widgets/TestWidget/css/style.css', /\.test-widget/); 150 | }); 151 | 152 | it('references nls in template', function() { 153 | assert.fileContent('widgets/TestWidget/Widget.html', /\$\{nls\./); 154 | }); 155 | 156 | it('has title/description in nls', function() { 157 | assert.fileContent('widgets/TestWidget/nls/strings.js', /widgetTitle: 'Test Widget'/); 158 | assert.fileContent('widgets/TestWidget/nls/strings.js', /description: 'A test widget\.'/); 159 | }); 160 | 161 | it('should set Label to _widgetTitle in NLS strings file', function() { 162 | assert.fileContent('widgets/TestWidget/nls/strings.js', /_widgetLabel: 'Test Widget'/); 163 | }); 164 | 165 | }); 166 | 167 | describe('when creating a widget w/o locale', function() { 168 | 169 | before(function(done) { 170 | helpers.run(generatorPath).withPrompts({ 171 | widgetName: 'TestWidget', 172 | widgetTitle: 'Test Widget', 173 | description: 'A test widget.', 174 | path: 'widgets', 175 | baseClass: 'test-widget', 176 | features: [ 'inPanel', 'hasStyle', 'hasConfig', 'hasUIFile' ] 177 | }).withLocalConfig({ 178 | jsVersion: 'ES2015' 179 | }) 180 | .on('end', done); 181 | }); 182 | 183 | it('creates expected files', function (/*done*/) { 184 | var expected = [ 185 | // add files you expect to exist here. 186 | 'widgets/TestWidget/Widget.js', 187 | 'widgets/TestWidget/Widget.html', 188 | 'widgets/TestWidget/config.json', 189 | 'widgets/TestWidget/css/style.css', 190 | 'widgets/TestWidget/images/icon.png', 191 | 'widgets/TestWidget/manifest.json' 192 | // TODO: settings 193 | ]; 194 | assert.file(expected); 195 | assert.noFile('widgets/TestWidget/nls/strings.js'); 196 | }); 197 | 198 | it('should set inPanel to true in manifest', function() { 199 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 200 | }); 201 | 202 | it('sets manifest hasLocale to false in manifest', function() { 203 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": false/); 204 | }); 205 | 206 | it('sets manifest hasConfig to true in manifest', function() { 207 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": true/); 208 | }); 209 | 210 | it('sets manifest hasStyle to true in manifest', function() { 211 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": true/); 212 | }); 213 | 214 | it('sets manifest hasUIFile to true in manifest', function() { 215 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": true/); 216 | }); 217 | 218 | it('has baseClass in widget', function() { 219 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 220 | }); 221 | 222 | it('has baseClass in css', function() { 223 | assert.fileContent('widgets/TestWidget/css/style.css', /\.test-widget/); 224 | }); 225 | 226 | it('does not reference nls in template', function() { 227 | assert.noFileContent('widgets/TestWidget/Widget.html', /\$\{nls\./); 228 | }); 229 | 230 | it('references title/description in template', function() { 231 | assert.fileContent('widgets/TestWidget/Widget.html', /Test Widget/); 232 | assert.fileContent('widgets/TestWidget/Widget.html', /A test widget\./); 233 | }); 234 | }); 235 | 236 | describe('when creating a widget w/o a setting page', function() { 237 | 238 | before(function(done) { 239 | helpers.run(generatorPath).withPrompts({ 240 | widgetName: 'TestWidget', 241 | widgetTitle: 'Test Widget', 242 | description: 'A test widget.', 243 | path: 'widgets', 244 | baseClass: 'test-widget', 245 | features: [ 'inPanel' ] 246 | }).withLocalConfig({ 247 | jsVersion: 'ES2015' 248 | }) 249 | .on('end', done); 250 | }); 251 | 252 | it('creates expected files', function (/*done*/) { 253 | assert.file([ 254 | 'widgets/TestWidget/Widget.js', 255 | 'widgets/TestWidget/manifest.json', 256 | 'widgets/TestWidget/images/icon.png' 257 | ]); 258 | 259 | assert.noFile([ 260 | 'widgets/TestWidget/Widget.html', 261 | 'widgets/TestWidget/config.json', 262 | 'widgets/TestWidget/css/style.css', 263 | 'widgets/TestWidget/nls/strings.js', 264 | ]); 265 | }); 266 | 267 | it('should set inPanel to true in manifest', function() { 268 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 269 | }); 270 | 271 | it('sets manifest hasLocale to false in manifest', function() { 272 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": false/); 273 | }); 274 | 275 | it('sets manifest hasConfig to true in manifest', function() { 276 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": false/); 277 | }); 278 | 279 | it('sets manifest hasStyle to true in manifest', function() { 280 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": false/); 281 | }); 282 | 283 | it('sets manifest hasUIFile to true in manifest', function() { 284 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingPage": false/); 285 | }); 286 | 287 | it('sets manifest hasSettingP to false in manifest', function() { 288 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": false/); 289 | }); 290 | 291 | it('has baseClass in widget', function() { 292 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 293 | }); 294 | }); 295 | 296 | describe('when creating a widget w/o style', function() { 297 | 298 | before(function(done) { 299 | helpers.run(generatorPath).withPrompts({ 300 | widgetName: 'TestWidget', 301 | widgetTitle: 'Test Widget', 302 | description: 'A test widget.', 303 | path: 'widgets', 304 | baseClass: 'test-widget', 305 | features: [ 'inPanel', 'hasLocale', 'hasConfig', 'hasUIFile' ] 306 | }).withLocalConfig({ 307 | jsVersion: 'ES2015' 308 | }) 309 | .on('end', done); 310 | }); 311 | 312 | it('creates expected files', function (/*done*/) { 313 | var expected = [ 314 | // add files you expect to exist here. 315 | 'widgets/TestWidget/Widget.js', 316 | 'widgets/TestWidget/Widget.html', 317 | 'widgets/TestWidget/config.json', 318 | 'widgets/TestWidget/nls/strings.js', 319 | 'widgets/TestWidget/images/icon.png', 320 | 'widgets/TestWidget/manifest.json' 321 | // TODO: settings 322 | ]; 323 | assert.file(expected); 324 | assert.noFile('widgets/TestWidget/css/style.css'); 325 | }); 326 | 327 | it('should set inPanel to true in manifest', function() { 328 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 329 | }); 330 | 331 | it('sets manifest hasLocale to true in manifest', function() { 332 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": true/); 333 | }); 334 | 335 | it('sets manifest hasStyle to false in manifest', function() { 336 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": false/); 337 | }); 338 | 339 | it('sets manifest hasUIFile to true in manifest', function() { 340 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": true/); 341 | }); 342 | 343 | it('sets manifest hasConfig to true in manifest', function() { 344 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": true/); 345 | }); 346 | 347 | it('has baseClass in widget', function() { 348 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 349 | }); 350 | 351 | it('references nls in template', function() { 352 | assert.fileContent('widgets/TestWidget/Widget.html', /\$\{nls\./); 353 | }); 354 | 355 | it('has title/description in nls', function() { 356 | assert.fileContent('widgets/TestWidget/nls/strings.js', /widgetTitle: 'Test Widget'/); 357 | assert.fileContent('widgets/TestWidget/nls/strings.js', /description: 'A test widget\.'/); 358 | }); 359 | 360 | it('should set Label to _widgetTitle in NLS strings file', function() { 361 | assert.fileContent('widgets/TestWidget/nls/strings.js', /_widgetLabel: 'Test Widget'/); 362 | }); 363 | 364 | }); 365 | 366 | describe('when creating a widget w/o config', function() { 367 | 368 | before(function(done) { 369 | helpers.run(generatorPath).withPrompts({ 370 | widgetName: 'TestWidget', 371 | widgetTitle: 'Test Widget', 372 | description: 'A test widget.', 373 | path: 'widgets', 374 | baseClass: 'test-widget', 375 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasUIFile' ] 376 | }).withLocalConfig({ 377 | jsVersion: 'ES2015' 378 | }) 379 | .on('end', done); 380 | }); 381 | 382 | it('creates expected files', function (/*done*/) { 383 | var expected = [ 384 | // add files you expect to exist here. 385 | 'widgets/TestWidget/Widget.js', 386 | 'widgets/TestWidget/Widget.html', 387 | 'widgets/TestWidget/css/style.css', 388 | 'widgets/TestWidget/nls/strings.js', 389 | 'widgets/TestWidget/images/icon.png', 390 | 'widgets/TestWidget/manifest.json' 391 | // TODO: settings 392 | ]; 393 | assert.file(expected); 394 | assert.noFile('widgets/TestWidget/config.json'); 395 | }); 396 | 397 | it('should set inPanel to true in manifest', function() { 398 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 399 | }); 400 | 401 | it('sets manifest hasLocale to true in manifest', function() { 402 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": true/); 403 | }); 404 | 405 | it('sets manifest hasStyle to true in manifest', function() { 406 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": true/); 407 | }); 408 | 409 | it('sets manifest hasUIFile to true in manifest', function() { 410 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": true/); 411 | }); 412 | 413 | it('sets manifest hasConfig to false in manifest', function() { 414 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": false/); 415 | }); 416 | 417 | it('has baseClass in widget', function() { 418 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 419 | }); 420 | 421 | it('references nls in template', function() { 422 | assert.fileContent('widgets/TestWidget/Widget.html', /\$\{nls\./); 423 | }); 424 | 425 | it('has title/description in nls', function() { 426 | assert.fileContent('widgets/TestWidget/nls/strings.js', /widgetTitle: 'Test Widget'/); 427 | assert.fileContent('widgets/TestWidget/nls/strings.js', /description: 'A test widget\.'/); 428 | }); 429 | 430 | it('should set Label to _widgetTitle in NLS strings file', function() { 431 | assert.fileContent('widgets/TestWidget/nls/strings.js', /_widgetLabel: 'Test Widget'/); 432 | }); 433 | 434 | }); 435 | 436 | 437 | describe('when creating a widget w/o template', function() { 438 | 439 | before(function(done) { 440 | helpers.run(generatorPath).withPrompts({ 441 | widgetName: 'TestWidget', 442 | widgetTitle: 'Test Widget', 443 | description: 'A test widget.', 444 | path: 'widgets', 445 | baseClass: 'test-widget', 446 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ] 447 | }).withLocalConfig({ 448 | jsVersion: 'ES2015' 449 | }) 450 | .on('end', done); 451 | }); 452 | 453 | it('creates expected files', function (/*done*/) { 454 | var expected = [ 455 | // add files you expect to exist here. 456 | 'widgets/TestWidget/Widget.js', 457 | 'widgets/TestWidget/config.json', 458 | 'widgets/TestWidget/css/style.css', 459 | 'widgets/TestWidget/nls/strings.js', 460 | 'widgets/TestWidget/images/icon.png', 461 | 'widgets/TestWidget/manifest.json' 462 | // TODO: settings 463 | ]; 464 | assert.file(expected); 465 | assert.noFile('widgets/TestWidget/Widget.html'); 466 | }); 467 | 468 | it('should set inPanel to true in manifest', function() { 469 | assert.fileContent('widgets/TestWidget/manifest.json', /"inPanel": true/); 470 | }); 471 | 472 | it('sets manifest hasLocale to true in manifest', function() { 473 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasLocale": true/); 474 | }); 475 | 476 | it('sets manifest hasStyle to true in manifest', function() { 477 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasStyle": true/); 478 | }); 479 | 480 | it('sets manifest hasConfig to true in manifest', function() { 481 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasConfig": true/); 482 | }); 483 | 484 | it('sets manifest hasUIFile to false in manifest', function() { 485 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasUIFile": false/); 486 | }); 487 | 488 | it('has baseClass in widget', function() { 489 | assert.fileContent('widgets/TestWidget/Widget.js', /baseClass: 'test-widget'/); 490 | }); 491 | 492 | it('has title/description in nls', function() { 493 | assert.fileContent('widgets/TestWidget/nls/strings.js', /widgetTitle: 'Test Widget'/); 494 | assert.fileContent('widgets/TestWidget/nls/strings.js', /description: 'A test widget\.'/); 495 | }); 496 | 497 | it('should set Label to _widgetTitle in NLS strings file', function() { 498 | assert.fileContent('widgets/TestWidget/nls/strings.js', /_widgetLabel: 'Test Widget'/); 499 | }); 500 | 501 | }); 502 | 503 | /** SETTINGS */ 504 | 505 | describe('when creating a widget with settings', function() { 506 | before(function(done) { 507 | helpers.run(generatorPath).withPrompts({ 508 | widgetName: 'TestWidget', 509 | widgetTitle: 'Test Widget', 510 | description: 'A test widget.', 511 | path: 'widgets', 512 | baseClass: 'test-widget', 513 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ], 514 | hasSettingPage: true, 515 | settingsFeatures: [ 'hasSettingUIFile', 'hasSettingLocale', 'hasSettingStyle' ] 516 | }).withLocalConfig({ 517 | jsVersion: 'ES2015' 518 | }) 519 | .on('end', done); 520 | }); 521 | 522 | it('creates expected files', function (/*done*/) { 523 | var expected = [ 524 | // add files you expect to exist here. 525 | 'widgets/TestWidget/Widget.js', 526 | 'widgets/TestWidget/config.json', 527 | 'widgets/TestWidget/css/style.css', 528 | 'widgets/TestWidget/nls/strings.js', 529 | 'widgets/TestWidget/images/icon.png', 530 | 'widgets/TestWidget/manifest.json', 531 | 'widgets/TestWidget/setting/Setting.js', 532 | 'widgets/TestWidget/setting/Setting.html', 533 | 'widgets/TestWidget/setting/css/style.css', 534 | 'widgets/TestWidget/setting/nls/strings.js' 535 | ]; 536 | assert.file(expected); 537 | }); 538 | 539 | it('should set hasSettingUIFile to true in manifest', function() { 540 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingUIFile": true/); 541 | }); 542 | 543 | it('should set hasSettingLocale to true in manifest', function() { 544 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingLocale": true/); 545 | }); 546 | 547 | it('should set hasSettingStyle to true in manifest', function() { 548 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingStyle": true/); 549 | }); 550 | 551 | it('has baseClass in Setting.js', function() { 552 | assert.fileContent('widgets/TestWidget/setting/Setting.js', /baseClass: 'test-widget-setting'/); 553 | }); 554 | }); 555 | 556 | describe('when creating a widget without settings', function() { 557 | before(function(done) { 558 | helpers.run(generatorPath).withPrompts({ 559 | widgetName: 'TestWidget', 560 | widgetTitle: 'Test Widget', 561 | description: 'A test widget.', 562 | path: 'widgets', 563 | baseClass: 'test-widget', 564 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ], 565 | hasSettingPage: false 566 | }).withLocalConfig({ 567 | jsVersion: 'ES2015' 568 | }) 569 | .on('end', done); 570 | }); 571 | 572 | it('creates expected files', function (/*done*/) { 573 | var expected = [ 574 | // add files you expect to exist here. 575 | 'widgets/TestWidget/Widget.js', 576 | 'widgets/TestWidget/config.json', 577 | 'widgets/TestWidget/css/style.css', 578 | 'widgets/TestWidget/nls/strings.js', 579 | 'widgets/TestWidget/images/icon.png', 580 | 'widgets/TestWidget/manifest.json' 581 | ]; 582 | assert.file(expected); 583 | assert.noFile('widgets/TestWidget/setting/Setting.js'); 584 | assert.noFile('widgets/TestWidget/setting/Setting.html'); 585 | assert.noFile('widgets/TestWidget/setting/css/style.css'); 586 | assert.noFile('widgets/TestWidget/setting/nls/strings.js'); 587 | }); 588 | 589 | it('should set hasSettingUIFile to false in manifest', function() { 590 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingUIFile": false/); 591 | }); 592 | 593 | it('should set hasSettingLocale to false in manifest', function() { 594 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingLocale": false/); 595 | }); 596 | 597 | it('should set hasSettingStyle to false in manifest', function() { 598 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingStyle": false/); 599 | }); 600 | }); 601 | 602 | describe('when creating a widget with settings without style', function() { 603 | before(function(done) { 604 | helpers.run(generatorPath).withPrompts({ 605 | widgetName: 'TestWidget', 606 | widgetTitle: 'Test Widget', 607 | description: 'A test widget.', 608 | path: 'widgets', 609 | baseClass: 'test-widget', 610 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ], 611 | hasSettingPage: true, 612 | settingsFeatures: [ 'hasSettingUIFile', 'hasSettingLocale' ] 613 | }).withLocalConfig({ 614 | jsVersion: 'ES2015' 615 | }) 616 | .on('end', done); 617 | 618 | 619 | }); 620 | 621 | it('creates expected files', function (/*done*/) { 622 | var expected = [ 623 | // add files you expect to exist here. 624 | 'widgets/TestWidget/Widget.js', 625 | 'widgets/TestWidget/config.json', 626 | 'widgets/TestWidget/css/style.css', 627 | 'widgets/TestWidget/nls/strings.js', 628 | 'widgets/TestWidget/images/icon.png', 629 | 'widgets/TestWidget/manifest.json', 630 | 'widgets/TestWidget/setting/Setting.js', 631 | 'widgets/TestWidget/setting/Setting.html', 632 | 'widgets/TestWidget/setting/nls/strings.js' 633 | ]; 634 | 635 | assert.file(expected); 636 | assert.noFile('widgets/TestWidget/setting/css/style.css'); 637 | }); 638 | 639 | it('should set hasSettingUIFile to true in manifest', function() { 640 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingUIFile": true/); 641 | }); 642 | 643 | it('should set hasSettingLocale to true in manifest', function() { 644 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingLocale": true/); 645 | }); 646 | 647 | it('should set hasSettingStyle to false in manifest', function() { 648 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingStyle": false/); 649 | }); 650 | }); 651 | 652 | describe('when creating a widget with settings without locale', function() { 653 | before(function(done) { 654 | helpers.run(generatorPath).withPrompts({ 655 | widgetName: 'TestWidget', 656 | widgetTitle: 'Test Widget', 657 | description: 'A test widget.', 658 | path: 'widgets', 659 | baseClass: 'test-widget', 660 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ], 661 | hasSettingPage: true, 662 | settingsFeatures: [ 'hasSettingUIFile', 'hasSettingStyle' ] 663 | }).withLocalConfig({ 664 | jsVersion: 'ES2015' 665 | }) 666 | .on('end', done); 667 | }); 668 | 669 | it('creates expected files', function (/*done*/) { 670 | var expected = [ 671 | // add files you expect to exist here. 672 | 'widgets/TestWidget/Widget.js', 673 | 'widgets/TestWidget/config.json', 674 | 'widgets/TestWidget/css/style.css', 675 | 'widgets/TestWidget/nls/strings.js', 676 | 'widgets/TestWidget/images/icon.png', 677 | 'widgets/TestWidget/manifest.json', 678 | 'widgets/TestWidget/setting/Setting.js', 679 | 'widgets/TestWidget/setting/Setting.html', 680 | 'widgets/TestWidget/setting/css/style.css' 681 | ]; 682 | assert.file(expected); 683 | assert.noFile('widgets/TestWidget/setting/nls/strings.js'); 684 | }); 685 | 686 | it('should set hasSettingUIFile to true in manifest', function() { 687 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingUIFile": true/); 688 | }); 689 | 690 | it('should set hasSettingLocale to false in manifest', function() { 691 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingLocale": false/); 692 | }); 693 | 694 | it('should set hasSettingStyle to true in manifest', function() { 695 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingStyle": true/); 696 | }); 697 | }); 698 | 699 | describe('when creating a widget with settings without UIFile', function() { 700 | before(function(done) { 701 | helpers.run(generatorPath).withPrompts({ 702 | widgetName: 'TestWidget', 703 | widgetTitle: 'Test Widget', 704 | description: 'A test widget.', 705 | path: 'widgets', 706 | baseClass: 'test-widget', 707 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig' ], 708 | hasSettingPage: true, 709 | settingsFeatures: [ 'hasSettingLocale', 'hasSettingStyle' ] 710 | }).withLocalConfig({ 711 | jsVersion: 'ES2015' 712 | }) 713 | .on('end', done); 714 | }); 715 | 716 | it('creates expected files', function (/*done*/) { 717 | var expected = [ 718 | // add files you expect to exist here. 719 | 'widgets/TestWidget/Widget.js', 720 | 'widgets/TestWidget/config.json', 721 | 'widgets/TestWidget/css/style.css', 722 | 'widgets/TestWidget/nls/strings.js', 723 | 'widgets/TestWidget/images/icon.png', 724 | 'widgets/TestWidget/manifest.json', 725 | 'widgets/TestWidget/setting/Setting.js', 726 | 'widgets/TestWidget/setting/css/style.css', 727 | 'widgets/TestWidget/setting/nls/strings.js' 728 | ]; 729 | 730 | assert.file(expected); 731 | assert.noFile('widgets/TestWidget/setting/Setting.html'); 732 | }); 733 | 734 | it('should set hasSettingUIFile to false in manifest', function() { 735 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingUIFile": false/); 736 | }); 737 | 738 | it('should set hasSettingLocale to true in manifest', function() { 739 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingLocale": true/); 740 | }); 741 | 742 | it('should set hasSettingStyle to true in manifest', function() { 743 | assert.fileContent('widgets/TestWidget/manifest.json', /"hasSettingStyle": true/); 744 | }); 745 | }); 746 | 747 | describe('when creating a widget that has a package.json', function() { 748 | before(function(done) { 749 | 750 | helpers.run(generatorPath).withPrompts({ 751 | widgetName: 'TestWidget', 752 | widgetTitle: 'Test Widget', 753 | description: 'A test widget.', 754 | path: 'widgets', 755 | baseClass: 'test-widget', 756 | features: [], 757 | hasSettingPage: false, 758 | settingsFeatures: [ ] 759 | }).withLocalConfig({ 760 | jsVersion: 'ES2015' 761 | }) 762 | .inTmpDir(function(dir) { 763 | console.log(path.join(dir, 'package.json')); 764 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":"' + testAuthorName + '", "license":"' + testLicense + '"}'); 765 | }) 766 | .on('end', done); 767 | }); 768 | 769 | it('has author name in manifest.json', function (/*done*/) { 770 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "' + testAuthorName + '",')); 771 | }); 772 | 773 | it('has license name in manifest.json', function (/*done*/) { 774 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 775 | }); 776 | }); 777 | 778 | describe('when creating a widget that has a package.json with author object - name only', function() { 779 | before(function(done) { 780 | 781 | helpers.run(generatorPath).withPrompts({ 782 | widgetName: 'TestWidget', 783 | widgetTitle: 'Test Widget', 784 | description: 'A test widget.', 785 | path: 'widgets', 786 | baseClass: 'test-widget', 787 | features: [], 788 | hasSettingPage: false, 789 | settingsFeatures: [ ] 790 | }).withLocalConfig({ 791 | jsVersion: 'ES2015' 792 | }) 793 | .inTmpDir(function(dir) { 794 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":{"name":"' + testAuthorName + '"}, "license":"' + testLicense + '"}'); 795 | }) 796 | .on('end', done); 797 | }); 798 | 799 | it('has author name in manifest.json', function (/*done*/) { 800 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "' + testAuthorName + '",')); 801 | }); 802 | 803 | it('has license name in manifest.json', function (/*done*/) { 804 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 805 | }); 806 | }); 807 | 808 | describe('when creating a widget that has a package.json with author object - author and email', function() { 809 | before(function(done) { 810 | 811 | helpers.run(generatorPath).withPrompts({ 812 | widgetName: 'TestWidget', 813 | widgetTitle: 'Test Widget', 814 | description: 'A test widget.', 815 | path: 'widgets', 816 | baseClass: 'test-widget', 817 | features: [], 818 | hasSettingPage: false, 819 | settingsFeatures: [ ] 820 | }).withLocalConfig({ 821 | jsVersion: 'ES2015' 822 | }) 823 | .inTmpDir(function(dir) { 824 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":{"name":"' + testAuthorName + '", "email":"' + testAuthorEmail + '"}, "license":"' + testLicense + '"}'); 825 | }) 826 | .on('end', done); 827 | }); 828 | 829 | it('has author name in manifest.json', function (/*done*/) { 830 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "' + testAuthorName + ' <' + testAuthorEmail + '>",')); 831 | }); 832 | 833 | it('has license name in manifest.json', function (/*done*/) { 834 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 835 | }); 836 | }); 837 | 838 | describe('when creating a widget that has a package.json with author object but no name property', function() { 839 | before(function(done) { 840 | 841 | helpers.run(generatorPath).withPrompts({ 842 | widgetName: 'TestWidget', 843 | widgetTitle: 'Test Widget', 844 | description: 'A test widget.', 845 | path: 'widgets', 846 | baseClass: 'test-widget', 847 | features: [], 848 | hasSettingPage: false, 849 | settingsFeatures: [ ] 850 | }).withLocalConfig({ 851 | jsVersion: 'ES2015' 852 | }) 853 | .inTmpDir(function(dir) { 854 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":{"url":"' + testAuthorUrl + '"}, "license":"' + testLicense + '"}'); 855 | }) 856 | .on('end', done); 857 | }); 858 | 859 | it('has blank author in manifest.json', function (/*done*/) { 860 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "",')); 861 | }); 862 | 863 | it('has license name in manifest.json', function (/*done*/) { 864 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 865 | }); 866 | }); 867 | 868 | describe('when creating a widget that has a package.json with author object - author and url', function() { 869 | before(function(done) { 870 | 871 | helpers.run(generatorPath).withPrompts({ 872 | widgetName: 'TestWidget', 873 | widgetTitle: 'Test Widget', 874 | description: 'A test widget.', 875 | path: 'widgets', 876 | baseClass: 'test-widget', 877 | features: [], 878 | hasSettingPage: false, 879 | settingsFeatures: [ ] 880 | }).withLocalConfig({ 881 | jsVersion: 'ES2015' 882 | }) 883 | .inTmpDir(function(dir) { 884 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":{"name":"' + testAuthorName + '", "url":"' + testAuthorUrl + '"}, "license":"' + testLicense + '"}'); 885 | }) 886 | .on('end', done); 887 | }); 888 | 889 | it('has author name in manifest.json', function (/*done*/) { 890 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "' + testAuthorName + ' \\(' + testAuthorUrl + '\\)"')); 891 | }); 892 | 893 | it('has license name in manifest.json', function (/*done*/) { 894 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 895 | }); 896 | }); 897 | 898 | describe('when creating a widget that has a package.json with author object - author, email, url', function() { 899 | before(function(done) { 900 | 901 | helpers.run(generatorPath).withPrompts({ 902 | widgetName: 'TestWidget', 903 | widgetTitle: 'Test Widget', 904 | description: 'A test widget.', 905 | path: 'widgets', 906 | baseClass: 'test-widget', 907 | features: [], 908 | hasSettingPage: false, 909 | settingsFeatures: [ ] 910 | }).withLocalConfig({ 911 | jsVersion: 'ES2015' 912 | }) 913 | .inTmpDir(function(dir) { 914 | fs.writeFileSync(path.join(dir, 'package.json'), '{"author":{"name":"' + testAuthorName + '", "email":"' + testAuthorEmail + '", "url":"' + testAuthorUrl + '"}, "license":"' + testLicense + '"}'); 915 | }) 916 | .on('end', done); 917 | }); 918 | 919 | it('has author name in manifest.json', function (/*done*/) { 920 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "' + testAuthorName + ' <' + testAuthorEmail + '> \\(' + testAuthorUrl + '\\)"')); 921 | }); 922 | 923 | it('has license name in manifest.json', function (/*done*/) { 924 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "' + testLicense + '",')); 925 | }); 926 | }); 927 | 928 | describe('when creating a widget that does not have a package.json', function() { 929 | before(function(done) { 930 | helpers.run(generatorPath).withPrompts({ 931 | widgetName: 'TestWidget', 932 | widgetTitle: 'Test Widget', 933 | description: 'A test widget.', 934 | path: 'widgets', 935 | baseClass: 'test-widget', 936 | features: [], 937 | hasSettingPage: false, 938 | settingsFeatures: [ ] 939 | }).withLocalConfig({ 940 | jsVersion: 'ES2015' 941 | }) 942 | .on('end', done); 943 | }); 944 | 945 | it('has blank author in manifest.json', function (/*done*/) { 946 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"author": "",')); 947 | }); 948 | 949 | it('has blank license in manifest.json', function (/*done*/) { 950 | assert.fileContent('widgets/TestWidget/manifest.json', new RegExp('"license": "",')); 951 | }); 952 | }); 953 | 954 | describe('when creating a widget that does not have the 2d/3d prompt set', function() { 955 | before(function(done) { 956 | helpers.run(generatorPath).withPrompts({ 957 | widgetName: 'TestWidget', 958 | widgetTitle: 'Test Widget', 959 | description: 'A test widget.', 960 | path: 'widgets', 961 | baseClass: 'test-widget', 962 | features: [], 963 | hasSettingPage: false, 964 | settingsFeatures: [ ] 965 | }).withLocalConfig({ 966 | jsVersion: 'ES2015' 967 | }) 968 | .on('end', done); 969 | }); 970 | 971 | it('has wabVersion set to 2.17', function (/*done*/) { 972 | assert.fileContent('widgets/TestWidget/manifest.json', /"wabVersion": "2.17",/); 973 | }); 974 | 975 | it('has platform set to HTML', function (/*done*/) { 976 | assert.fileContent('widgets/TestWidget/manifest.json', /"platform": "HTML",/); 977 | }); 978 | 979 | it('has 2D set to true', function (/*done*/) { 980 | assert.fileContent('widgets/TestWidget/manifest.json', /"2D": true,/); 981 | }); 982 | 983 | it('has 3D set to false', function (/*done*/) { 984 | assert.fileContent('widgets/TestWidget/manifest.json', /"3D": false/); 985 | }); 986 | }); 987 | 988 | 989 | describe('when creating a widget that has the 2d/3d prompt set to 2d', function() { 990 | before(function(done) { 991 | 992 | helpers.run(generatorPath).withPrompts({ 993 | widgetName: 'TestWidget', 994 | widgetTitle: 'Test Widget', 995 | description: 'A test widget.', 996 | path: 'widgets', 997 | baseClass: 'test-widget', 998 | features: [], 999 | hasSettingPage: false, 1000 | settingsFeatures: [ ] 1001 | }).withLocalConfig({ 1002 | widgetsType: "is2d", 1003 | jsVersion: 'ES2015' 1004 | }) 1005 | .on('end', done); 1006 | }); 1007 | 1008 | it('has wabVersion set to 2.17', function (/*done*/) { 1009 | assert.fileContent('widgets/TestWidget/manifest.json', /"wabVersion": "2.17"/); 1010 | }); 1011 | 1012 | it('has platform set to HTML', function (/*done*/) { 1013 | assert.fileContent('widgets/TestWidget/manifest.json', /"platform": "HTML"/); 1014 | }); 1015 | 1016 | it('has 2D set to true', function (/*done*/) { 1017 | assert.fileContent('widgets/TestWidget/manifest.json', /"2D": true/); 1018 | }); 1019 | 1020 | it('has 3D set to false', function (/*done*/) { 1021 | assert.fileContent('widgets/TestWidget/manifest.json', /"3D": false/); 1022 | }); 1023 | }); 1024 | 1025 | describe('when creating a widget that has the 2d/3d prompt set to 3d', function() { 1026 | before(function(done) { 1027 | 1028 | helpers.run(generatorPath).withPrompts({ 1029 | widgetName: 'TestWidget', 1030 | widgetTitle: 'Test Widget', 1031 | description: 'A test widget.', 1032 | path: 'widgets', 1033 | baseClass: 'test-widget', 1034 | features: [], 1035 | hasSettingPage: false, 1036 | settingsFeatures: [ ] 1037 | }).withLocalConfig({ 1038 | widgetsType: "is3d", 1039 | jsVersion: 'ES2015' 1040 | }) 1041 | .on('end', done); 1042 | }); 1043 | 1044 | it('has wabVersion set to 2.17', function (/*done*/) { 1045 | assert.fileContent('widgets/TestWidget/manifest.json', /"wabVersion": "2.17"/); 1046 | }); 1047 | 1048 | it('has platform set to HTML3D', function (/*done*/) { 1049 | assert.fileContent('widgets/TestWidget/manifest.json', /"platform": "HTML3D"/); 1050 | }); 1051 | 1052 | it('has 2D set to false', function (/*done*/) { 1053 | assert.fileContent('widgets/TestWidget/manifest.json', /"2D": false/); 1054 | }); 1055 | 1056 | it('has 3D set to true', function (/*done*/) { 1057 | assert.fileContent('widgets/TestWidget/manifest.json', /"3D": true/); 1058 | }); 1059 | }); 1060 | 1061 | 1062 | 1063 | describe('when creating a widget where the user chose to use sass', function() { 1064 | before(function(done) { 1065 | 1066 | helpers.run(generatorPath).withPrompts({ 1067 | widgetName: 'TestWidget', 1068 | widgetTitle: 'Test Widget', 1069 | description: 'A test widget.', 1070 | path: 'widgets', 1071 | baseClass: 'test-widget', 1072 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile' ], 1073 | hasSettingPage: true, 1074 | settingsFeatures: [ 'hasSettingUIFile', 'hasSettingLocale', 'hasSettingStyle' ], 1075 | }).withLocalConfig({ 1076 | useSass: true, 1077 | jsVersion: 'ES2015' 1078 | }) 1079 | .on('end', done); 1080 | }); 1081 | 1082 | 1083 | it('creates expected scss style file', function (/*done*/) { 1084 | assert.file('widgets/TestWidget/css/style.scss'); 1085 | }); 1086 | it('does not create css style file', function (/*done*/) { 1087 | assert.noFile('widgets/TestWidget/css/style.css'); 1088 | }); 1089 | it('creates settings scss style file', function (/*done*/) { 1090 | assert.file('widgets/TestWidget/setting/css/style.scss'); 1091 | }); 1092 | it('does not create settings css style file', function (/*done*/) { 1093 | assert.noFile('widgets/TestWidget/setting/css/style.css'); 1094 | }); 1095 | }); 1096 | 1097 | describe('when creating a widget where the user chose NOT to use sass', function() { 1098 | before(function(done) { 1099 | 1100 | helpers.run(generatorPath).withPrompts({ 1101 | widgetName: 'TestWidget', 1102 | widgetTitle: 'Test Widget', 1103 | description: 'A test widget.', 1104 | path: 'widgets', 1105 | baseClass: 'test-widget', 1106 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile' ], 1107 | }).withLocalConfig({ 1108 | useSass: false, 1109 | jsVersion: 'ES2015' 1110 | }) 1111 | .on('end', done); 1112 | }); 1113 | 1114 | it('creates expected css style file', function (/*done*/) { 1115 | assert.file('widgets/TestWidget/css/style.css'); 1116 | }); 1117 | it('does not create scss style file', function (/*done*/) { 1118 | assert.noFile('widgets/TestWidget/css/style.scss'); 1119 | }); 1120 | }); 1121 | 1122 | describe('when creating a TypeScript widget', function() { 1123 | before(function(done) { 1124 | 1125 | helpers.run(generatorPath).withPrompts({ 1126 | widgetName: 'TestWidget', 1127 | widgetTitle: 'Test Widget', 1128 | description: 'A test widget.', 1129 | path: 'widgets', 1130 | baseClass: 'test-widget', 1131 | features: [ 'inPanel', 'hasLocale', 'hasStyle', 'hasConfig', 'hasUIFile' ], 1132 | }).withLocalConfig({ 1133 | useSass: false, 1134 | jsVersion: 'TypeScript' 1135 | }) 1136 | .on('end', done); 1137 | }); 1138 | 1139 | it('creates expected widget.ts file', function (/*done*/) { 1140 | assert.file('widgets/TestWidget/Widget.ts'); 1141 | }); 1142 | it('creates expected support/declareDecorator.ts file', function (/*done*/) { 1143 | assert.file('widgets/TestWidget/support/declareDecorator.ts'); 1144 | }); 1145 | it('creates expected config.ts file', function (/*done*/) { 1146 | assert.file('widgets/TestWidget/config.ts'); 1147 | }); 1148 | it('does not create widget.js file', function (/*done*/) { 1149 | assert.noFile('widgets/TestWidget/Widget.js'); 1150 | }); 1151 | it('creates expected setting/setting.ts file', function (/*done*/) { 1152 | assert.file('widgets/TestWidget/setting/Setting.ts'); 1153 | }); 1154 | it('does not create setting/setting.js file', function (/*done*/) { 1155 | assert.noFile('widgets/TestWidget/setting/Setting.js'); 1156 | }); 1157 | 1158 | it('imports the config', function (/*done*/) { 1159 | assert.fileContent('widgets/TestWidget/Widget.ts', /import IConfig from '.\/config';/); 1160 | }); 1161 | }); 1162 | 1163 | 1164 | }); 1165 | --------------------------------------------------------------------------------