Repository: jhipster/generator-jhipster
Pull request #9314 (2019-02-21 11:30:54)

[v5 maintenance] Fix react angular css api and needles
Same as #9151 bur for v5 maintenance branch

-   [x] [Travis tests](https://travis-ci.org/jhipster/generator-jhipster/pull_requests) are green
-   [x] Tests are added where necessary
-   [x] Documentation is added/updated where necessary
-   [x] Coding Rules & Commit Guidelines as per our [CONTRIBUTING.md document](https://github.com/jhipster/generator-jhipster/blob/master/CONTRIBUTING.md) are followed

<!--
Please also reference the issue number in a commit message to [automatically close the related Github issue](https://help.github.com/articles/closing-issues-via-commit-messages/)

Note: It is also possible to add `[skip ci]` to your commit message to skip Travis tests
-->



Patch:
diff --git a/generators/client/templates/angular/src/main/webapp/content/scss/global.scss.ejs b/generators/client/templates/angular/src/main/webapp/content/scss/global.scss.ejs
index 4c9e639663..81ec4bc399 100644
--- a/generators/client/templates/angular/src/main/webapp/content/scss/global.scss.ejs
+++ b/generators/client/templates/angular/src/main/webapp/content/scss/global.scss.ejs
@@ -236,4 +236,4 @@ ui bootstrap tweaks
     white-space: nowrap;
 }
 
-/* jhipster-needle-scss-add-global JHipster will add new css style */
+/* jhipster-needle-scss-add-main JHipster will add new css style */
diff --git a/generators/client/templates/react/src/main/webapp/app/app.scss.ejs b/generators/client/templates/react/src/main/webapp/app/app.scss.ejs
index f58fc18afe..8b4f5d7ba2 100644
--- a/generators/client/templates/react/src/main/webapp/app/app.scss.ejs
+++ b/generators/client/templates/react/src/main/webapp/app/app.scss.ejs
@@ -336,4 +336,4 @@ http://stackoverflow.com/questions/23436430/bootstrap-3-input-group-100-width */
   margin-top: -0.5rem;
 }
 
-/* jhipster-needle-css-add-main JHipster will add new css style */
+/* jhipster-needle-scss-add-main JHipster will add new css style */
diff --git a/generators/generator-base.js b/generators/generator-base.js
index eca521e7bb..aa5e6cf030 100644
--- a/generators/generator-base.js
+++ b/generators/generator-base.js
@@ -569,7 +569,7 @@ module.exports = class extends PrivateBase {
     }
 
     /**
-     * Add new css or scss style to the angular application in "global.css" or "global.scss".
+     * Add new css style to the angular application in "global.css.
      *
      * @param {string} style - css to add in the file
      * @param {string} comment - comment to add before css code
@@ -587,78 +587,55 @@ module.exports = class extends PrivateBase {
      * }
      *
      */
-    addGlobalCSSStyle(style, comment) {
-        if (this.clientFramework !== 'angularX') {
-            this.error('Global css is only supported by Angular, for React @see addAppCSSStyle()');
-            return;
-        }
-
-        if (this.useSass) {
-            this.needleApi.clientAngular.addGlobalSCSSStyle(style, comment);
-        } else {
-            this.needleApi.clientAngular.addGlobalCSSStyle(style, comment);
-        }
+    addMainCSSStyle(style, comment) {
+        this.needleApi.clientAngular.addGlobalCSSStyle(style, comment);
     }
 
     /**
-     * Add new scss style to the angular application in "vendor.scss".
+     * Add new scss style to the angular application in "global.scss
      *
-     * @param {string} style - scss to add in the file
+     * @param {string} style - css to add in the file
      * @param {string} comment - comment to add before css code
      *
      * example:
      *
-     * style = '.success {\n     @extend .message;\n    border-color: green;\n}'
-     * comment = 'Message'
+     * style = '.jhipster {\n     color: #baa186;\n}'
+     * comment = 'New JHipster color'
      *
      * * ==========================================================================
-     * Message
+     * New JHipster color
      * ========================================================================== *
-     * .success {
-     *     @extend .message;
-     *     border-color: green;
+     * .jhipster {
+     *     color: #baa186;
      * }
      *
      */
-    addVendorSCSSStyle(style, comment) {
-        if (this.clientFramework !== 'angularX') {
-            this.error('Vendor is only supported by Angular');
-            return;
-        }
-
-        this.needleApi.clientAngular.addVendorSCSSStyle(style, comment);
+    addMainSCSSStyle(style, comment) {
+        this.needleApi.clientAngular.addGlobalSCSSStyle(style, comment);
     }
 
     /**
-     * Add new css or scss style to the react application in "app.css" or "app.scss".
+     * Add new scss style to the angular application in "vendor.scss".
      *
-     * @param {string} style - css to add in the file
+     * @param {string} style - scss to add in the file
      * @param {string} comment - comment to add before css code
      *
      * example:
      *
-     * style = '.jhipster {\n     color: #baa186;\n}'
-     * comment = 'New JHipster color'
+     * style = '.success {\n     @extend .message;\n    border-color: green;\n}'
+     * comment = 'Message'
      *
      * * ==========================================================================
-     * New JHipster color
+     * Message
      * ========================================================================== *
-     * .jhipster {
-     *     color: #baa186;
+     * .success {
+     *     @extend .message;
+     *     border-color: green;
      * }
      *
      */
-    addAppCSSStyle(style, comment) {
-        if (this.clientFramework !== 'react') {
-            this.error('App css is only supported by React');
-            return;
-        }
-
-        if (this.useSass) {
-            this.needleApi.clientReact.addAppSCSSStyle(style, comment);
-        } else {
-            this.needleApi.clientReact.addAppCSSStyle(style, comment);
-        }
+    addVendorSCSSStyle(style, comment) {
+        this.needleApi.clientAngular.addVendorSCSSStyle(style, comment);
     }
 
     /**
diff --git a/generators/internal/needle-api/needle-client-angular.js b/generators/internal/needle-api/needle-client-angular.js
index 7e2681529e..b63b9c0631 100644
--- a/generators/internal/needle-api/needle-client-angular.js
+++ b/generators/internal/needle-api/needle-client-angular.js
@@ -27,12 +27,12 @@ const CLIENT_MAIN_SRC_DIR = constants.CLIENT_MAIN_SRC_DIR;
 module.exports = class extends needleClientBase {
     addGlobalCSSStyle(style, comment) {
         const filePath = `${CLIENT_MAIN_SRC_DIR}content/css/global.css`;
-        this.addStyle(style, comment, filePath, 'jhipster-needle-css-add-global');
+        this.addStyle(style, comment, filePath, 'jhipster-needle-css-add-main');
     }
 
     addGlobalSCSSStyle(style, comment) {
         const filePath = `${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`;
-        this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-global');
+        this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main');
     }
 
     addVendorSCSSStyle(style, comment) {
diff --git a/generators/internal/needle-api/needle-client-react.js b/generators/internal/needle-api/needle-client-react.js
index 5679937a0a..a2168c91b5 100644
--- a/generators/internal/needle-api/needle-client-react.js
+++ b/generators/internal/needle-api/needle-client-react.js
@@ -27,12 +27,12 @@ const CLIENT_MAIN_SRC_DIR = constants.CLIENT_MAIN_SRC_DIR;
 module.exports = class extends needleClientBase {
     addAppCSSStyle(style, comment) {
         const filePath = `${CLIENT_MAIN_SRC_DIR}app/app.css`;
-        this.addStyle(style, comment, filePath, 'jhipster-needle-css-add-app');
+        this.addStyle(style, comment, filePath, 'jhipster-needle-css-add-main');
     }
 
     addAppSCSSStyle(style, comment) {
         const filePath = `${CLIENT_MAIN_SRC_DIR}app/app.scss`;
-        this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-app');
+        this.addStyle(style, comment, filePath, 'jhipster-needle-scss-add-main');
     }
 
     addEntityToMenu(routerName, enableTranslation, entityTranslationKeyMenu) {


Test patch:
diff --git a/test/needle-api/needle-base.spec.js b/test/needle-api/needle-base.spec.js
new file mode 100644
index 0000000000..9871f4c74d
--- /dev/null
+++ b/test/needle-api/needle-base.spec.js
@@ -0,0 +1,39 @@
+const assert = require('yeoman-assert');
+const NeedleApiBase = require('../../generators/internal/needle-api/needle-base');
+
+describe('Unit tests for needle-base API', () => {
+    before(done => {
+        this.needleApiBase = new NeedleApiBase();
+        done();
+    });
+    describe('generate a file model without path', () => {
+        before(done => {
+            this.generatedModel = this.needleApiBase.generateFileModel('dummyFile', 'a-needle-tag', '<p>My content added</p>');
+            done();
+        });
+        it('creates expected default files for server and angularX', () => {
+            assert.textEqual(this.generatedModel.file, 'dummyFile');
+            assert.textEqual(this.generatedModel.needle, 'a-needle-tag');
+            assert.objectContent(this.generatedModel.splicable, new Array('<p>My content added</p>'));
+        });
+    });
+
+    describe('generate a file model with a path', () => {
+        before(done => {
+            this.generatedModel = this.needleApiBase.generateFileModelWithPath(
+                'aPath',
+                'dummyFile',
+                'a-needle-tag',
+                '<p>My content added</p>'
+            );
+            done();
+        });
+
+        it('creates expected default files for server and angularX', () => {
+            assert.textEqual(this.generatedModel.path, 'aPath');
+            assert.textEqual(this.generatedModel.file, 'dummyFile');
+            assert.textEqual(this.generatedModel.needle, 'a-needle-tag');
+            assert.objectContent(this.generatedModel.splicable, new Array('<p>My content added</p>'));
+        });
+    });
+});
diff --git a/test/needle-api/needle-client-angular.spec.js b/test/needle-api/needle-client-angular.spec.js
new file mode 100644
index 0000000000..a632f77543
--- /dev/null
+++ b/test/needle-api/needle-client-angular.spec.js
@@ -0,0 +1,168 @@
+const path = require('path');
+const assert = require('yeoman-assert');
+const helpers = require('yeoman-test');
+const ClientGenerator = require('../../generators/client');
+const constants = require('../../generators/generator-constants');
+
+const CLIENT_MAIN_SRC_DIR = constants.CLIENT_MAIN_SRC_DIR;
+
+const mockBlueprintSubGen = class extends ClientGenerator {
+    constructor(args, opts) {
+        super(args, { fromBlueprint: true, ...opts }); // fromBlueprint variable is important
+        const jhContext = (this.jhipsterContext = this.options.jhipsterContext);
+        if (!jhContext) {
+            this.error("This is a JHipster blueprint and should be used only like 'jhipster --blueprint myblueprint')}");
+        }
+        this.configOptions = jhContext.configOptions || {};
+        // This sets up options for this sub generator and is being reused from JHipster
+        jhContext.setupClientOptions(this, jhContext);
+    }
+
+    get initializing() {
+        return super._initializing();
+    }
+
+    get prompting() {
+        return super._prompting();
+    }
+
+    get configuring() {
+        return super._configuring();
+    }
+
+    get default() {
+        return super._default();
+    }
+
+    get writing() {
+        const phaseFromJHipster = super._writing();
+        const customPhaseSteps = {
+            addCssStylesProperty() {
+                if (this.useSass) {
+                    this.addMainSCSSStyle('@import style_without_comment');
+                    this.addMainSCSSStyle('@import style', 'my comment');
+                    this.addVendorSCSSStyle('@import style', 'my comment');
+                    this.addVendorSCSSStyle('@import style_without_comment');
+                } else {
+                    this.addMainCSSStyle('without-comment { font-size: 200%; color: red; }');
+                    this.addMainCSSStyle('h1 { font-size: 200%; color: navy; }', 'my comment');
+                }
+            }
+        };
+        return { ...phaseFromJHipster, ...customPhaseSteps };
+    }
+
+    get install() {
+        return super._install();
+    }
+
+    get end() {
+        return super._end();
+    }
+};
+
+describe('needle API SCSS: JHipster client generator with blueprint', () => {
+    const blueprintNames = ['generator-jhipster-myblueprint', 'myblueprint'];
+
+    blueprintNames.forEach(blueprintName => {
+        describe(`generate client with blueprint option '${blueprintName}'`, () => {
+            before(done => {
+                helpers
+                    .run(path.join(__dirname, '../../generators/client'))
+                    .withOptions({
+                        'from-cli': true,
+                        build: 'maven',
+                        auth: 'jwt',
+                        db: 'mysql',
+                        skipInstall: true,
+                        blueprint: blueprintName,
+                        skipChecks: true
+                    })
+                    .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']])
+                    .withPrompts({
+                        baseName: 'jhipster',
+                        clientFramework: 'angularX',
+                        useSass: true,
+                        enableTranslation: true,
+                        nativeLanguage: 'en',
+                        languages: ['fr']
+                    })
+                    .on('end', done);
+            });
+
+            it('vendor.scss contains the specific change (without comment) added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /@import style_without_comment/);
+            });
+
+            it('global.scss contains the specific change (without comment) added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /@import style_without_comment/);
+            });
+
+            it('vendor.scss contains the specific change added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`, /@import style/);
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}content/scss/vendor.scss`,
+                    '* ==========================================================================\n' +
+                        'my comment\n' +
+                        '========================================================================== */\n'
+                );
+            });
+
+            it('global.scss contains the specific change added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`, /@import style/);
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}content/scss/global.scss`,
+                    '* ==========================================================================\n' +
+                        'my comment\n' +
+                        '========================================================================== */\n'
+                );
+            });
+        });
+    });
+});
+
+describe('needle API CSS: JHipster client generator with blueprint', () => {
+    const blueprintNames = ['generator-jhipster-myblueprint', 'myblueprint'];
+
+    blueprintNames.forEach(blueprintName => {
+        describe(`generate client with blueprint option '${blueprintName}'`, () => {
+            before(done => {
+                helpers
+                    .run(path.join(__dirname, '../../generators/client'))
+                    .withOptions({
+                        'from-cli': true,
+                        build: 'maven',
+                        auth: 'jwt',
+                        db: 'mysql',
+                        skipInstall: true,
+                        blueprint: blueprintName,
+                        skipChecks: true
+                    })
+                    .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']])
+                    .withPrompts({
+                        baseName: 'jhipster',
+                        clientFramework: 'angularX',
+                        useSass: false,
+                        enableTranslation: true,
+                        nativeLanguage: 'en',
+                        languages: ['fr']
+                    })
+                    .on('end', done);
+            });
+
+            it('global.css contains the specific change (without comment) added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/css/global.css`, /without-comment { font-size: 200%; color: red; }/);
+            });
+
+            it('global.css contains the specific change added by needle api', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}content/css/global.css`, /h1 { font-size: 200%; color: navy; }/);
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}content/css/global.css`,
+                    '* ==========================================================================\n' +
+                        'my comment\n' +
+                        '========================================================================== */\n'
+                );
+            });
+        });
+    });
+});
diff --git a/test/needle-api/needle-client-react.spec.js b/test/needle-api/needle-client-react.spec.js
new file mode 100644
index 0000000000..162ded0567
--- /dev/null
+++ b/test/needle-api/needle-client-react.spec.js
@@ -0,0 +1,184 @@
+const path = require('path');
+const assert = require('yeoman-assert');
+const helpers = require('yeoman-test');
+const ClientGenerator = require('../../generators/client');
+const constants = require('../../generators/generator-constants');
+
+const CLIENT_MAIN_SRC_DIR = constants.CLIENT_MAIN_SRC_DIR;
+
+const mockBlueprintSubGen = class extends ClientGenerator {
+    constructor(args, opts) {
+        super(args, { fromBlueprint: true, ...opts }); // fromBlueprint variable is important
+
+        const jhContext = (this.jhipsterContext = this.options.jhipsterContext);
+
+        if (!jhContext) {
+            this.error('This is a JHipster blueprint and should be used only like jhipster --blueprint myblueprint');
+        }
+
+        this.configOptions = jhContext.configOptions || {};
+        // This sets up options for this sub generator and is being reused from JHipster
+        jhContext.setupEntityOptions(this, jhContext, this);
+    }
+
+    get initializing() {
+        return super._initializing();
+    }
+
+    get prompting() {
+        return super._prompting();
+    }
+
+    get configuring() {
+        return super._configuring();
+    }
+
+    get default() {
+        return super._default();
+    }
+
+    get writing() {
+        const phaseFromJHipster = super._writing();
+        const customPhaseSteps = {
+            addAppCssStep() {
+                if (this.useSass) {
+                    // please change this to public API when it will be available see https://github.com/jhipster/generator-jhipster/issues/9234
+                    this.needleApi.clientReact.addAppSCSSStyle('@import without-comment');
+                    this.needleApi.clientReact.addAppSCSSStyle('@import with-comment', 'my comment');
+                } else {
+                    this.needleApi.clientReact.addAppCSSStyle('without-comment { color:red; }');
+                    this.needleApi.clientReact.addAppCSSStyle('with-comment { color:red; }', 'my comment');
+                }
+            },
+            addEntityToMenuStep() {
+                this.addEntityToMenu('routerName', false, 'react', false);
+            },
+            addEntityToModuleStep() {
+                this.addEntityToModule(
+                    'entityInstance',
+                    'entityClass',
+                    'entityName',
+                    'entityFolderName',
+                    'entityFileName',
+                    'entityUrl',
+                    'react',
+                    'microServiceNam'
+                );
+            }
+        };
+        return { ...phaseFromJHipster, ...customPhaseSteps };
+    }
+};
+
+describe('needle API React: JHipster client generator with blueprint', () => {
+    const blueprintNames = ['generator-jhipster-myblueprint', 'myblueprint'];
+
+    blueprintNames.forEach(blueprintName => {
+        describe(`generate client with blueprint option '${blueprintName}'`, () => {
+            before(done => {
+                helpers
+                    .run(path.join(__dirname, '../../generators/client'))
+                    .withOptions({
+                        'from-cli': true,
+                        build: 'maven',
+                        auth: 'jwt',
+                        db: 'mysql',
+                        skipInstall: true,
+                        blueprint: blueprintName,
+                        skipChecks: true
+                    })
+                    .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']])
+                    .withPrompts({
+                        baseName: 'jhipster',
+                        clientFramework: 'react',
+                        useSass: false,
+                        enableTranslation: true,
+                        nativeLanguage: 'en',
+                        languages: ['en', 'fr']
+                    })
+                    .on('end', done);
+            });
+
+            it('Assert app.css is updated', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}app/app.css`, 'without-comment { color:red; }');
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}app/app.css`, 'with-comment { color:red; }');
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}app/app.css`,
+                    '* ==========================================================================\n' +
+                        'my comment\n' +
+                        '========================================================================== */\n'
+                );
+            });
+
+            it('Assert entity is added to menu', () => {
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}app/shared/layout/header/menus/entities.tsx`,
+                    '<DropdownItem tag={Link} to="/entity/routerName">\n' +
+                        '      <FontAwesomeIcon icon="asterisk" fixedWidth />&nbsp;Router Name\n' +
+                        '    </DropdownItem>'
+                );
+            });
+
+            it('Assert entity is added to module', () => {
+                const indexModulePath = `${CLIENT_MAIN_SRC_DIR}app/entities/index.tsx`;
+                const indexReducerPath = `${CLIENT_MAIN_SRC_DIR}app/shared/reducers/index.ts`;
+
+                assert.fileContent(indexModulePath, "import entityName from './entityFolderName';");
+                assert.fileContent(indexModulePath, '<ErrorBoundaryRoute path={`${match.url}/entityFileName`} component={entityName} />'); // eslint-disable-line
+
+                assert.fileContent(
+                    indexReducerPath,
+                    '// prettier-ignore\n' +
+                        'import entityInstance, {\n' +
+                        '  entityNameState\n' +
+                        "} from 'app/entities/entityFolderName/entityFileName.reducer';"
+                );
+                assert.fileContent(indexReducerPath, 'readonly entityInstance: entityNameState;');
+                assert.fileContent(indexReducerPath, 'entityInstance,');
+            });
+        });
+    });
+});
+
+describe('needle API React: JHipster client generator with blueprint', () => {
+    const blueprintNames = ['generator-jhipster-myblueprint', 'myblueprint'];
+
+    blueprintNames.forEach(blueprintName => {
+        describe(`generate client with blueprint option '${blueprintName}'`, () => {
+            before(done => {
+                helpers
+                    .run(path.join(__dirname, '../../generators/client'))
+                    .withOptions({
+                        'from-cli': true,
+                        build: 'maven',
+                        auth: 'jwt',
+                        db: 'mysql',
+                        skipInstall: true,
+                        blueprint: blueprintName,
+                        skipChecks: true
+                    })
+                    .withGenerators([[mockBlueprintSubGen, 'jhipster-myblueprint:client']])
+                    .withPrompts({
+                        baseName: 'jhipster',
+                        clientFramework: 'react',
+                        useSass: true,
+                        enableTranslation: true,
+                        nativeLanguage: 'en',
+                        languages: ['en', 'fr']
+                    })
+                    .on('end', done);
+            });
+
+            it('Assert app.scss is updated', () => {
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import without-comment');
+                assert.fileContent(`${CLIENT_MAIN_SRC_DIR}app/app.scss`, '@import with-comment');
+                assert.fileContent(
+                    `${CLIENT_MAIN_SRC_DIR}app/app.scss`,
+                    '* ==========================================================================\n' +
+                        'my comment\n' +
+                        '========================================================================== */\n'
+                );
+            });
+        });
+    });
+});