From 58add4329989c77d2b1d9963cac41f3354ee82bd Mon Sep 17 00:00:00 2001
From: Stanislav Lvovsky <stanislav.lvovsky@sap.com>
Date: Wed, 10 Jun 2020 11:17:05 +0300
Subject: [PATCH] Types (#300)

* support multiple types

* Update package.json

* Update .nycrc.json

* add helper method to filter.ts

* Update .nycrc.json

* cr fixes

* update foodq
---
 backend/.nycrc.json                      |  6 ++--
 backend/package.json                     |  2 +-
 backend/src/filter.ts                    | 28 +++++++++++++-----
 backend/src/vscode-youi-events.ts        |  2 +-
 backend/src/yeomanui.ts                  | 10 +++----
 backend/tests/filter.spec.ts             | 19 +++++++++----
 backend/tests/vscode-youi-events.spec.ts |  4 +--
 backend/tests/yeomanui.spec.ts           | 36 ++++++++++++------------
 generator-foodq/package.json             |  5 +++-
 9 files changed, 68 insertions(+), 44 deletions(-)

diff --git a/backend/.nycrc.json b/backend/.nycrc.json
index ce200a9df..608987bff 100644
--- a/backend/.nycrc.json
+++ b/backend/.nycrc.json
@@ -8,8 +8,8 @@
     "temp-dir": "./reports/.nyc_output",
     "report-dir": "./reports/coverage",
     "check-coverage": true,
-    "branches": 79,
+    "branches": 78.9,
     "lines": 85,
-    "functions": 78,
-    "statements": 85
+    "functions": 78.8,
+    "statements": 84.6
   }
diff --git a/backend/package.json b/backend/package.json
index 4cd6e34e4..257c69950 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "yeoman-ui",
-	"version": "1.0.13",
+	"version": "1.0.14",
 	"displayName": "Application Wizard",
 	"publisher": "SAPOS",
 	"author": {
diff --git a/backend/src/filter.ts b/backend/src/filter.ts
index 0926837bc..b7fc081e4 100644
--- a/backend/src/filter.ts
+++ b/backend/src/filter.ts
@@ -2,8 +2,7 @@ import * as _ from "lodash";
 
 export enum GeneratorType {
     project = "project", 
-    module = "module",
-    all = "all"
+    module = "module"
 }
 
 function getCategories(filterObject?: any): string[] {
@@ -20,17 +19,32 @@ function getCategories(filterObject?: any): string[] {
     return [];
 }
 
-function getType(filterObject?: any): GeneratorType {
-    return _.get(filterObject, "type", GeneratorType.all);
+function getTypes(filterObject?: any): string[] {
+    let types: string[] = [];
+    const objectTypes: any = _.get(filterObject, "types", _.get(filterObject, "type"));
+    if (_.isString(objectTypes)) {
+        types.push(objectTypes);
+    } else if (_.isArray(objectTypes)) {
+        // leave only string values
+        types = _.filter(objectTypes, type => {
+            return _.isString(type);
+        });
+    }
+
+    return _.compact(_.map(types, _.trim));
 }
 
 export class GeneratorFilter {
     public static create(filterObject?: any) {
         const categories: string[] = getCategories(filterObject); 
-        const type: GeneratorType = getType(filterObject);
+        const types: string[] = getTypes(filterObject);
+
+        return new GeneratorFilter(types, categories);
+    }
 
-        return new GeneratorFilter(type, categories);
+    public static hasIntersection(array1: string[], array2: string[]) {
+        return (_.isEmpty(array1) || !_.isEmpty(_.intersection(array1, array2)))
     }
 
-    constructor(public readonly  type: GeneratorType, public readonly categories: string[]) {}
+    constructor(public readonly types: string[], public readonly categories: string[]) {}
 }
diff --git a/backend/src/vscode-youi-events.ts b/backend/src/vscode-youi-events.ts
index 43ea1717c..7194ed554 100644
--- a/backend/src/vscode-youi-events.ts
+++ b/backend/src/vscode-youi-events.ts
@@ -59,7 +59,7 @@ export class VSCodeYouiEvents implements YouiEvents {
             
             const targetFolderUri: vscode.Uri = vscode.Uri.file(targetFolderPath);
 
-            if (this.genFilter.type !== GeneratorType.module) {
+            if (_.indexOf(this.genFilter.types, GeneratorType.module) === -1) {
                 const workspacePath = _.get(vscode, "workspace.workspaceFolders[0].uri.fsPath");
                 // 1. target workspace folder should not already contain target generator folder
                 const foundInWorkspace = _.find(vscode.workspace.workspaceFolders, (wsFolder: vscode.WorkspaceFolder) => {
diff --git a/backend/src/yeomanui.ts b/backend/src/yeomanui.ts
index 88e2a093a..693d9b3ba 100644
--- a/backend/src/yeomanui.ts
+++ b/backend/src/yeomanui.ts
@@ -14,7 +14,7 @@ import { YouiLog } from "./youi-log";
 import { YouiEvents } from "./youi-events";
 import { IRpc } from "@sap-devx/webview-rpc/out.ext/rpc-common";
 import Generator = require("yeoman-generator");
-import { GeneratorType, GeneratorFilter } from "./filter";
+import { GeneratorFilter, GeneratorType } from "./filter";
 import { IChildLogger } from "@vscode-logging/logger";
 import {IPrompt} from "@sap-devx/yeoman-ui-types";
 
@@ -375,7 +375,7 @@ export class YeomanUI {
 
     const questions: any[] = [];
 
-    if (genFilter.type === GeneratorType.project) {
+    if (_.indexOf(genFilter.types, GeneratorType.project) > 0) {
       const defaultPath = this.getCwd();
       const targetFolderQuestion: any = {
         type: "input",
@@ -429,9 +429,9 @@ export class YeomanUI {
     }
 
     const genFilter: GeneratorFilter = GeneratorFilter.create(_.get(packageJson, ["generator-filter"]));
-    const typeEqual: boolean = (filter.type === GeneratorType.all || filter.type === genFilter.type);
-    const categoriesHasIntersection: boolean = (_.isEmpty(filter.categories) || !_.isEmpty(_.intersection(filter.categories, genFilter.categories)));
-    if (typeEqual && categoriesHasIntersection) {
+    const typesHasIntersection: boolean = GeneratorFilter.hasIntersection(filter.types, genFilter.types);
+    const categoriesHasIntersection: boolean = GeneratorFilter.hasIntersection(filter.categories, genFilter.categories);
+    if (typesHasIntersection && categoriesHasIntersection) {
       return this.createGeneratorChoice(genName, genPackagePath, packageJson);
     }
 
diff --git a/backend/tests/filter.spec.ts b/backend/tests/filter.spec.ts
index 8407de1af..fe7c83e3e 100644
--- a/backend/tests/filter.spec.ts
+++ b/backend/tests/filter.spec.ts
@@ -1,7 +1,7 @@
 import * as mocha from "mocha";
 import { expect } from "chai";
 
-import {GeneratorFilter, GeneratorType} from "../src/filter";
+import {GeneratorFilter} from "../src/filter";
 
 describe('filter unit test', () => {
     it('categories property is not of array type', () => {
@@ -18,28 +18,35 @@ describe('filter unit test', () => {
         const testCategories: string[] = ["test1", "test2"];
         const genFilter: GeneratorFilter = GeneratorFilter.create({type: "test123", categories: testCategories});
         // tslint:disable-next-line: no-unused-expression
-        expect(genFilter.type).to.be.equal("test123");
+        expect(genFilter.types).to.contain("test123");
         expect(genFilter.categories).to.be.deep.equal(testCategories);
     });
 
     it('filter obj is undefined', () => {
         const genFilter: GeneratorFilter = GeneratorFilter.create(undefined);
         // tslint:disable-next-line: no-unused-expression
-        expect(genFilter.type).to.be.equal(GeneratorType.all);
+        expect(genFilter.types).to.be.empty;
+        expect(genFilter.categories).to.be.deep.equal([]);
+    });
+
+    it('filter type is neither array nor string', () => {
+        const genFilter: GeneratorFilter = GeneratorFilter.create({types: {}});
+        // tslint:disable-next-line: no-unused-expression
+        expect(genFilter.types).to.be.empty;
         expect(genFilter.categories).to.be.deep.equal([]);
     });
 
     it('type property is project and category property has strings in array ', () => {
         const testCategories: string[] = ["test1", "test2"];
         const genFilter: GeneratorFilter = GeneratorFilter.create({type: "project", categories: testCategories});
-        expect(genFilter.type).to.be.equal(GeneratorType.project);
+        expect(genFilter.types).to.contain("project");
         expect(genFilter.categories).to.be.deep.equal(testCategories);
     });
 
     it('type property is module and category property has strings in array ', () => {
         const testCategories: string[] = ["test1", "test2"];
-        const genFilter: GeneratorFilter = GeneratorFilter.create({type: "module", categories: testCategories});
-        expect(genFilter.type).to.be.equal(GeneratorType.module);
+        const genFilter: GeneratorFilter = GeneratorFilter.create({types: ["  module"], categories: testCategories});
+        expect(genFilter.types).to.contain("module");
         expect(genFilter.categories).to.be.deep.equal(testCategories);
     });
 });
\ No newline at end of file
diff --git a/backend/tests/vscode-youi-events.spec.ts b/backend/tests/vscode-youi-events.spec.ts
index 2a1b17859..1bafc02ec 100644
--- a/backend/tests/vscode-youi-events.spec.ts
+++ b/backend/tests/vscode-youi-events.spec.ts
@@ -3,7 +3,7 @@ import { expect } from "chai";
 import * as sinon from "sinon";
 import * as _ from "lodash";
 import * as vscode from "vscode";
-import { GeneratorFilter, GeneratorType } from '../src/filter';
+import { GeneratorFilter } from '../src/filter';
 
 import { VSCodeYouiEvents } from "../src/vscode-youi-events";
 
@@ -106,7 +106,7 @@ describe('vscode-youi-events unit test', () => {
         });
 
         it("generator filter type is module", () => {
-            const genFilter = GeneratorFilter.create({type: GeneratorType.module});
+            const genFilter = GeneratorFilter.create({type: ["module"]});
             const events = new VSCodeYouiEvents(undefined, undefined, genFilter);
             eventsMock = sandbox.mock(events);
             eventsMock.expects("doClose");
diff --git a/backend/tests/yeomanui.spec.ts b/backend/tests/yeomanui.spec.ts
index 795fcb0ec..a202090f5 100644
--- a/backend/tests/yeomanui.spec.ts
+++ b/backend/tests/yeomanui.spec.ts
@@ -11,7 +11,7 @@ import * as yeomanEnv from "yeoman-environment";
 import { YouiLog } from "../src/youi-log";
 import { YouiEvents } from '../src/youi-events';
 import { IMethod, IPromiseCallbacks, IRpc } from "@sap-devx/webview-rpc/out.ext/rpc-common";
-import { GeneratorType, GeneratorFilter } from "../src/filter";
+import { GeneratorFilter } from "../src/filter";
 import { IChildLogger } from "@vscode-logging/logger";
 import * as os from "os";
 import { fail } from "assert";
@@ -233,13 +233,13 @@ describe('yeomanui unit test', () => {
             fsExtraMock.expects("readFile").withExactArgs(path.join("test4Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project"}, "description": "test4Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test5Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test5Description"}`);
 
-            const genFilter: GeneratorFilter = GeneratorFilter.create({type: GeneratorType.project});
+            const genFilter: GeneratorFilter = GeneratorFilter.create({type: "project"});
             yeomanUi["uiOptions"] = {genFilter, messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
-            expect(result.questions[1].choices).to.have.lengthOf(2);
-            const test1Choice = result.questions[1].choices[0];
-            const test2Choice = result.questions[1].choices[1];
+            expect(result.questions[0].choices).to.have.lengthOf(2);
+            const test1Choice = result.questions[0].choices[0];
+            const test2Choice = result.questions[0].choices[1];
             expect(test1Choice.name).to.be.equal("Test1");
             expect(test1Choice.description).to.be.equal("test1Description");
             expect(test2Choice.name).to.be.equal("Test4");
@@ -272,7 +272,7 @@ describe('yeomanui unit test', () => {
             fsExtraMock.expects("readFile").withExactArgs(path.join("test4Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project"}, "description": "test4Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test5Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test5Description"}`);
 
-            const genFilter = GeneratorFilter.create({type: GeneratorType.module});
+            const genFilter = GeneratorFilter.create({type: "module"});
             yeomanUi["uiOptions"] = {genFilter, messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
@@ -312,7 +312,7 @@ describe('yeomanui unit test', () => {
             fsExtraMock.expects("readFile").withExactArgs(path.join("test5Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test5Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test6Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "all"}}`);
 
-            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: GeneratorType.all}), messages: {}};
+            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: []}), messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
             expect(result.questions[0].choices).to.have.lengthOf(6);
@@ -344,7 +344,7 @@ describe('yeomanui unit test', () => {
             fsExtraMock.expects("readFile").withExactArgs(path.join("test4Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project"}, "description": "test4Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test5Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test5Description"}`);
 
-            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: GeneratorType.all}), messages: {}};
+            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: []}), messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
             expect(result.questions[0].choices).to.have.lengthOf(3);
@@ -360,11 +360,11 @@ describe('yeomanui unit test', () => {
 
             fsExtraMock.expects("readFile").withExactArgs(path.join("test1Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project123"}, "description": "test4Description"}`);
 
-            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: GeneratorType.project}), messages: {}};
+            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: "project"}), messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
             // tslint:disable-next-line: no-unused-expression
-            expect(result.questions[1].choices).to.be.empty;
+            expect(result.questions[0].choices).to.be.empty;
         });
 
         it("get generators with type project and categories cat1 and cat2", async () => {
@@ -387,20 +387,20 @@ describe('yeomanui unit test', () => {
             });
             envMock.expects("getGeneratorNames").returns(["test1", "test2", "test3", "test4", "test5"]);
 
-            fsExtraMock.expects("readFile").withExactArgs(path.join("test1Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project", "categories": ["cat2"]}, "description": "test1Description"}`);
+            fsExtraMock.expects("readFile").withExactArgs(path.join("test1Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": ["project"], "categories": ["cat2"]}, "description": "test1Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test2Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project", "categories": ["cat2", "cat1"]}}`);
-            fsExtraMock.expects("readFile").withExactArgs(path.join("test3Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "module"}}`);
+            fsExtraMock.expects("readFile").withExactArgs(path.join("test3Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": ["module"]}}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test4Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "project", "categories": ["cat1"]}, "description": "test4Description"}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test5Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test5Description"}`);
 
-            const genFilter: GeneratorFilter = GeneratorFilter.create({type: GeneratorType.project, categories: ["cat1", "cat2"]});
+            const genFilter: GeneratorFilter = GeneratorFilter.create({type: ["project"], categories: ["cat1", "cat2"]});
             yeomanUi["uiOptions"].genFilter = genFilter;
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
-            expect(result.questions[1].choices).to.have.lengthOf(3);
-            const test1Choice = result.questions[1].choices[0];
-            const test2Choice = result.questions[1].choices[1];
-            const test3Choice = result.questions[1].choices[2];
+            expect(result.questions[0].choices).to.have.lengthOf(3);
+            const test1Choice = result.questions[0].choices[0];
+            const test2Choice = result.questions[0].choices[1];
+            const test3Choice = result.questions[0].choices[2];
             expect(test1Choice.name).to.be.equal("Test1");
             expect(test2Choice.name).to.be.equal("Test2");
             expect(test3Choice.name).to.be.equal("Test4");
@@ -424,7 +424,7 @@ describe('yeomanui unit test', () => {
             fsExtraMock.expects("readFile").withExactArgs(path.join("test2Path", PACKAGE_JSON), UTF8).resolves(`{"generator-filter": {"type": "module"}}`);
             fsExtraMock.expects("readFile").withExactArgs(path.join("test3Path", PACKAGE_JSON), UTF8).resolves(`{"description": "test3Description", "displayName": "3rd - Test"}`);
 
-            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: GeneratorType.all}), messages: {}};
+            yeomanUi["uiOptions"] = {genFilter: GeneratorFilter.create({type: undefined}), messages: {}};
             const result = await yeomanUi["getGeneratorsPrompt"]();
 
             const choices = result.questions[0].choices;
diff --git a/generator-foodq/package.json b/generator-foodq/package.json
index 6c83f5d87..d2ef207d7 100644
--- a/generator-foodq/package.json
+++ b/generator-foodq/package.json
@@ -29,6 +29,9 @@
     "@sap-devx/yeoman-ui-types": "0.0.1"
   },
   "generator-filter": {
-    "type": "project"
+    "types": [
+      "foodq",
+      "project"
+    ]
   }
 }