igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,275 lines (1,172 loc) • 56.9 kB
text/typescript
import { EmptyTree } from "@angular-devkit/schematics";
import { UnitTestTree } from "@angular-devkit/schematics/testing";
import * as fs from "fs";
import * as path from "path";
import {
ClassChanges,
BindingChanges,
SelectorChanges,
ThemeChanges,
ImportsChanges,
ElementType,
ThemeType,
} from "./schema";
import {
UpdateChanges,
InputPropertyType,
BoundPropertyObject,
} from "./UpdateChanges";
import * as tsUtils from "./tsUtils";
describe("UpdateChanges", () => {
let appTree: UnitTestTree;
class UnitUpdateChanges extends UpdateChanges {
public getSelectorChanges() {
return this.selectorChanges;
}
public getClassChanges() {
return this.classChanges;
}
public getOutputChanges() {
return this.outputChanges;
}
public getInputChanges() {
return this.inputChanges;
}
public getThemeChanges() {
return this.themeChanges;
}
public getImportsChanges() {
return this.importsChanges;
}
}
beforeEach(() => {
appTree = new UnitTestTree(new EmptyTree());
appTree.create(
"/angular.json",
JSON.stringify({
projects: {
testProj: {
sourceRoot: "/",
},
},
})
);
});
it("should replace/remove components", (done) => {
const selectorsJson: SelectorChanges = {
changes: [
{
type: "component" as any,
selector: "igx-component",
replaceWith: "igx-replaced",
},
{
type: "component" as any,
selector: "igx-remove",
remove: true,
},
],
};
const jsonPath = path.join(__dirname, "changes", "selectors.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn(fs, "readFileSync").and.returnValue(
JSON.stringify(selectorsJson)
);
appTree.create(
"test.component.html",
"<igx-component> <content> <igx-remove></igx-remove> </igx-component> <igx-remove> <content> </igx-remove>"
);
appTree.create(
"test2.component.html",
"<igx-remove attr></igx-remove><igx-component>"
);
appTree.create(
"test3.component.html",
"<igx-remove-me-not attr></igx-remove-me-not> <igx-component> <igx-component-child></igx-component-child> </igx-component>"
);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getSelectorChanges()).toEqual(selectorsJson);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
"<igx-replaced> <content> </igx-replaced> "
);
expect(appTree.readContent("test2.component.html")).toEqual(
"<igx-replaced>"
);
expect(appTree.readContent("test3.component.html")).toEqual(
"<igx-remove-me-not attr></igx-remove-me-not> <igx-replaced> <igx-component-child></igx-component-child> </igx-replaced>"
);
done();
});
it("should replace/remove directives", (done) => {
const selectorsJson: SelectorChanges = {
changes: [
{
type: "directive" as any,
selector: "igxDirective",
replaceWith: "igxReplaced",
},
{
type: "directive" as any,
selector: "igxRemove",
remove: true,
},
],
};
const jsonPath = path.join(__dirname, "changes", "selectors.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn(fs, "readFileSync").and.returnValue(
JSON.stringify(selectorsJson)
);
appTree.create(
"test.component.html",
`<igx-component [igxDirective]="val" igxRemove> <content igxDirective [igxRemove]="val"> </igx-component>` +
`<igx-component2 [igxRemove]='val'> <content> </igx-component2>`
);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(update.getSelectorChanges()).toEqual(selectorsJson);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<igx-component [igxReplaced]="val"> <content igxReplaced> </igx-component>` +
`<igx-component2> <content> </igx-component2>`
);
done();
});
it("should replace/remove outputs", (done) => {
const outputJson: BindingChanges = {
changes: [
{
name: "onReplaceMe",
replaceWith: "replaced",
owner: { type: "component" as any, selector: "comp" },
},
{
name: "onOld",
remove: true,
owner: { type: "component" as any, selector: "another" },
},
],
};
const jsonPath = path.join(__dirname, "changes", "outputs.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(outputJson)
);
const fileContent = `<one (onReplaceMe)="a"> <comp\r\ntag (onReplaceMe)="dwdw" (onOld)=""> </other> <another (onOld)="b" />`;
appTree.create("test.component.html", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getOutputChanges()).toEqual(outputJson);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<one (onReplaceMe)="a"> <comp\r\ntag (replaced)="dwdw" (onOld)=""> </other> <another />`
);
// should only match the defined selector #11666
const fileContent2 = `<comp (onReplaceMe)="a"> <comp-not-same (onReplaceMe)="..NOT"> <another (onOld)="b" /> <another-diff (onOld)="toKeep" />`;
appTree.overwrite("test.component.html", fileContent2);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<comp (replaced)="a"> <comp-not-same (onReplaceMe)="..NOT"> <another /> <another-diff (onOld)="toKeep" />`
);
outputJson.changes[0].owner = {
type: "directive" as any,
selector: "tag",
};
outputJson.changes[1].owner = {
type: "directive" as any,
selector: "tag",
};
appTree.overwrite("test.component.html", fileContent);
const update2 = new UnitUpdateChanges(__dirname, appTree);
update2.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<one (onReplaceMe)="a"> <comp\r\ntag (replaced)="dwdw"> </other> <another (onOld)="b" />`
);
done();
});
it("should replace/remove inputs", (done) => {
const inputJson: BindingChanges = {
changes: [
{
name: "replaceMe",
replaceWith: "replaced",
owner: { type: "component" as any, selector: "comp" },
},
{
name: "oldProp",
remove: true,
owner: { type: "component" as any, selector: "another" },
},
],
};
const jsonPath = path.join(__dirname, "changes", "inputs.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(inputJson)
);
let fileContent = `<one [replaceMe]="a"> <comp\r\ntag [replaceMe]="dwdw" [oldProp]=''> </other> <another oldProp="b" />`;
appTree.create("test.component.html", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getInputChanges()).toEqual(inputJson);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<one [replaceMe]="a"> <comp\r\ntag [replaced]="dwdw" [oldProp]=''> </other> <another />`
);
inputJson.changes[1].remove = false;
inputJson.changes[1].replaceWith = "oldReplaced";
appTree.overwrite("test.component.html", fileContent);
const update2 = new UnitUpdateChanges(__dirname, appTree);
update2.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<one [replaceMe]="a"> <comp\r\ntag [replaced]="dwdw" [oldProp]=''> </other> <another oldReplaced="b" />`
);
inputJson.changes[1].remove = true;
inputJson.changes[0].owner = {
type: "directive" as any,
selector: "tag",
};
inputJson.changes[1].owner = {
type: "directive" as any,
selector: "tag",
};
appTree.overwrite("test.component.html", fileContent);
const update3 = new UnitUpdateChanges(__dirname, appTree);
update3.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<one [replaceMe]="a"> <comp\r\ntag [replaced]="dwdw"> </other> <another oldProp="b" />`
);
inputJson.changes[1].owner = {
type: "component" as any,
selector: "another",
};
inputJson.changes[0].owner = {
type: "component" as any,
selector: "comp",
};
const fileContent2 = `<comp\r\ntag [oldProp]="g" [replaceMe]="NOT.replaceMe" ><another oldProp="g" [otherProp]="oldProp" /></comp>`;
appTree.overwrite("test.component.html", fileContent2);
const update4 = new UnitUpdateChanges(__dirname, appTree);
update4.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<comp\r\ntag [oldProp]="g" [replaced]="NOT.replaceMe" ><another [otherProp]="oldProp" /></comp>`
);
fileContent = `<span [bait]="replaceMe"><ng-container ngProjectAs="comp"> sike! </ng-container></span>`;
appTree.overwrite("test.component.html", fileContent);
update4.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<span [bait]="replaceMe"><ng-container ngProjectAs="comp"> sike! </ng-container></span>`
);
// should only match the defined selector #11666
fileContent = `<comp [replaceMe]="dwdw"> <comp-not-same [replaceMe]="..NOT"> <another oldProp="b" /> <another-diff oldProp="toKeep" />`;
appTree.overwrite("test.component.html", fileContent);
update4.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<comp [replaced]="dwdw"> <comp-not-same [replaceMe]="..NOT"> <another /> <another-diff oldProp="toKeep" />`
);
done();
});
it("should replace class identifiers", (done) => {
const classJson: ClassChanges = {
changes: [
{
name: "igxClass",
replaceWith: "igxReplace",
},
{
name: "igxClass2",
replaceWith: "igxSecond",
},
],
};
const jsonPath = path.join(__dirname, "changes", "classes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(classJson)
);
const fileContent = `import { igxClass, igxClass2 } from "igniteui-angular-sovn"; export class Test { prop: igxClass; prop2: igxClass2; }`;
appTree.create("test.component.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getClassChanges()).toEqual(classJson);
update.applyChanges();
expect(appTree.readContent("test.component.ts")).toEqual(
`import { igxReplace, igxSecond } from "igniteui-angular-sovn"; export class Test { prop: igxReplace; prop2: igxSecond; }`
);
done();
});
it("should replace multiple class identifier with the same value", (done) => {
const classJson: ClassChanges = {
changes: [
{
name: "igxClass",
replaceWith: "igxReplace",
},
{
name: "igxClass2",
replaceWith: "igxReplace",
},
],
};
const jsonPath = path.join(__dirname, "changes", "classes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(classJson)
);
const fileContent = `import { igxClass, igxClass2 } from "igniteui-angular-sovn"; export class Test { prop: igxClass; prop2: igxClass2; }`;
appTree.create("test.component.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getClassChanges()).toEqual(classJson);
update.applyChanges();
expect(appTree.readContent("test.component.ts")).toEqual(
`import { igxReplace } from "igniteui-angular-sovn"; export class Test { prop: igxReplace; prop2: igxReplace; }`
);
done();
});
it("should replace class identifiers (complex file)", (done) => {
const classJson: ClassChanges = {
changes: [
{ name: "IgxGridComponent", replaceWith: "IgxGridReplace" },
{ name: "IgxColumnComponent", replaceWith: "IgxColumnReplace" },
{ name: "IgxProvided", replaceWith: "IgxProvidedReplace" },
{ name: "STRING_FILTERS", replaceWith: "REPLACED_CONST" },
{ name: "IgxCsvExporterService", replaceWith: "Injected" },
{ name: "IgxExcelExporterOptions", replaceWith: "IgxNewable" },
// partial match:
{ name: "IgxExporterOptionsBase", replaceWith: "ReturnType" },
// no actual matches:
{ name: "NotType", replaceWith: "Nope" },
{ name: "NotAgain", replaceWith: "NopeAgain" },
],
};
const jsonPath = path.join(__dirname, "changes", "classes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(classJson)
);
const fileContent =
`import { Component, Injectable, ViewChild } from "@angular/core";` +
`import { IgxGridComponent } from "igniteui-angular-sovn";` +
`import { IgxColumnComponent, IgxProvided, STRING_FILTERS} from "igniteui-angular-sovn";\r\n` +
`import {` +
` IgxCsvExporterService,` +
` IgxExcelExporterOptions,` +
` IgxExporterOptionsBase` +
`} from "igniteui-angular-sovn";\r\n` +
`@Component({` +
` providers: [IgxProvided, RemoteService]` +
`})` +
`export class GridSampleComponent {` +
` @ViewChild("grid1", { read: IgxGridComponent }) public grid1: IgxGridComponent;` +
` // prop definitions to ignore:\r\n` +
` NotType: { NotAgain: string; extraProp: IgxExcelExporterOptions, IgxExcelExporterOptions: string } = {` +
` NotAgain: "hai",` +
` extraProp: new IgxExcelExporterOptions(),` +
` IgxExcelExporterOptions: "fake"` +
` }` +
` constructor(private csvExporterService: IgxCsvExporterService) { }` +
` public initColumns(event: IgxColumnComponent) {` +
` const column: IgxColumnComponent = event;` +
` this.grid1.filter("ProductName", "Queso", STRING_FILTERS.contains, true);` +
` }` +
` private getOptions(fileName: string): IgxExporterOptionsBase {` +
` return new IgxExcelExporterOptions(fileName);` +
` }` +
`}`;
appTree.create("test.component.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getClassChanges()).toEqual(classJson);
update.applyChanges();
expect(appTree.readContent("test.component.ts")).toEqual(
`import { Component, Injectable, ViewChild } from "@angular/core";` +
`import { IgxGridReplace } from "igniteui-angular-sovn";` +
`import { IgxColumnReplace, IgxProvidedReplace, REPLACED_CONST} from "igniteui-angular-sovn";\r\n` +
`import {` +
` Injected,` +
` IgxNewable,` +
` ReturnType` +
`} from "igniteui-angular-sovn";\r\n` +
`@Component({` +
` providers: [IgxProvidedReplace, RemoteService]` +
`})` +
`export class GridSampleComponent {` +
` @ViewChild("grid1", { read: IgxGridReplace }) public grid1: IgxGridReplace;` +
` // prop definitions to ignore:\r\n` +
` NotType: { NotAgain: string; extraProp: IgxNewable, IgxExcelExporterOptions: string } = {` +
` NotAgain: "hai",` +
` extraProp: new IgxNewable(),` +
` IgxExcelExporterOptions: "fake"` +
` }` +
` constructor(private csvExporterService: Injected) { }` +
` public initColumns(event: IgxColumnReplace) {` +
` const column: IgxColumnReplace = event;` +
` this.grid1.filter("ProductName", "Queso", REPLACED_CONST.contains, true);` +
` }` +
` private getOptions(fileName: string): ReturnType {` +
` return new IgxNewable(fileName);` +
` }` +
`}`
);
done();
});
it("should correctly ignore types not from igniteui-angular-sovn", () => {
const classJson: ClassChanges = {
changes: [
{ name: "Name", replaceWith: "NameName" },
{ name: "Another", replaceWith: "Other" },
],
};
const jsonPath = path.join(__dirname, "changes", "classes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(classJson)
);
const fileContent = `import { Name } from ""; import { Another } from "@space/package"; export class Test { prop: Name; prop2: Another; }`;
appTree.create("test.component.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(update.getClassChanges()).toEqual(classJson);
spyOn(tsUtils, "getRenamePositions").and.callThrough();
update.applyChanges();
expect(tsUtils.getRenamePositions).toHaveBeenCalledWith(
"/test.component.ts",
"Name",
jasmine.anything()
);
expect(tsUtils.getRenamePositions).toHaveBeenCalledWith(
"/test.component.ts",
"Another",
jasmine.anything()
);
expect(appTree.readContent("test.component.ts")).toEqual(fileContent);
});
it("should correctly rename aliased imports and handle collision from other packages", () => {
const classJson: ClassChanges = {
changes: [
{ name: "Type", replaceWith: "IgxType" },
{ name: "Size", replaceWith: "IgxSize" },
{ name: "IgxService", replaceWith: "IgxService1" },
{ name: "IgxDiffService", replaceWith: "IgxNewDiffService" },
{ name: "Calendar", replaceWith: "CalendarActual" },
],
};
const jsonPath = path.join(__dirname, "changes", "classes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(classJson)
);
let fileContent = `import { Size, Type as someThg } from "igniteui-angular-sovn";
import { IgxService, IgxDiffService as eDiffService, Calendar as Calendar } from 'igniteui-angular-sovn';
import { Type } from "@angular/core";
export class Test {
prop: Type;
prop1: someThg;
prop2: Size = { prop: "Size" };
secondary: eDiffService;
cal: Calendar;
constructor (public router: Router, private _iconService: IgxService) {}
}`;
appTree.create("test.component.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getClassChanges()).toEqual(classJson);
update.applyChanges();
let expectedFileContent = `import { IgxSize, IgxType as someThg } from "igniteui-angular-sovn";
import { IgxService1, IgxNewDiffService as eDiffService, CalendarActual as Calendar } from 'igniteui-angular-sovn';
import { Type } from "@angular/core";
export class Test {
prop: Type;
prop1: someThg;
prop2: IgxSize = { prop: "Size" };
secondary: eDiffService;
cal: Calendar;
constructor (public router: Router, private _iconService: IgxService1) {}
}`;
expect(appTree.readContent("test.component.ts")).toEqual(
expectedFileContent
);
// with ig feed package:
fileContent = fileContent.replace(
/igniteui-angular-sovn/g,
"@infragistics/igniteui-angular-sovn"
);
expectedFileContent = expectedFileContent.replace(
/igniteui-angular-sovn/g,
"@infragistics/igniteui-angular-sovn"
);
appTree.overwrite("test.component.ts", fileContent);
update.applyChanges();
expect(appTree.readContent("test.component.ts")).toEqual(
expectedFileContent
);
});
it("should move property value between element tags", (done) => {
const inputJson: BindingChanges = {
changes: [
{
name: "name",
moveBetweenElementTags: true,
conditions: ["igxIcon_is_material_name"],
owner: { type: "component" as any, selector: "igx-icon" },
},
],
};
const jsonPath = path.join(__dirname, "changes", "inputs.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(inputJson)
);
const fileContent = `<igx-icon fontSet='material' name='phone'></igx-icon>
<igx-icon fontSet="material-icons" name="build"></igx-icon>
<igx-icon name="accessory"></igx-icon>`;
appTree.create("test.component.html", fileContent);
const fileContent1 = `<igx-icon fontSet="material" [name]="'phone'"></igx-icon>
<igx-icon fontSet="material-icons" [name]="getName()"></igx-icon>`;
appTree.create("test1.component.html", fileContent1);
const update = new UnitUpdateChanges(__dirname, appTree);
update.addCondition("igxIcon_is_material_name", () => true);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getInputChanges()).toEqual(inputJson);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<igx-icon fontSet='material'>phone</igx-icon>
<igx-icon fontSet="material-icons">build</igx-icon>
<igx-icon>accessory</igx-icon>`
);
expect(appTree.readContent("test1.component.html")).toEqual(
`<igx-icon fontSet="material">{{'phone'}}</igx-icon>
<igx-icon fontSet="material-icons">{{getName()}}</igx-icon>`
);
done();
});
it("should replace/remove properties", (done) => {
const themeChangesJson: ThemeChanges = {
changes: [
{
name: "$replace-me",
replaceWith: "$replaced",
owner: "igx-theme-func",
type: ThemeType.Property,
},
{
name: "$remove-me",
remove: true,
owner: "igx-theme-func",
type: ThemeType.Property,
},
{
name: "$old-prop",
remove: true,
owner: "igx-comp-theme",
type: ThemeType.Property,
},
],
};
const jsonPath = path.join(__dirname, "changes", "theme-changes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(themeChangesJson)
);
const fileContent = `$var: igx-theme-func(
$prop1: red,
$replace-me: 3,
$remove-me: 0px,
$prop2: 2
);
$var2: igx-comp-theme(
$replace-me: not,
$old-prop: func(val)
);
$var3: igx-comp-theme(
$replace-me: not,
$old-prop: func(val, 3, 4),
$prop3: 1
);`;
appTree.create("styles.scss", fileContent);
appTree.create(
"src/app/app.component.scss",
`igx-comp-theme($replace-me: not, $old-prop: 3, $prop3: 2);`
);
appTree.create(
"test.component.scss",
`igx-theme-func($replace-me: 10px, $old-prop: 3, $prop3: 2);`
);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getThemeChanges()).toEqual(themeChangesJson);
update.applyChanges();
expect(appTree.readContent("styles.scss")).toEqual(
`$var: igx-theme-func(
$prop1: red,
$replaced: 3,
$prop2: 2
);
$var2: igx-comp-theme(
$replace-me: not
);
$var3: igx-comp-theme(
$replace-me: not,
$prop3: 1
);`
);
expect(appTree.readContent("src/app/app.component.scss")).toEqual(
`igx-comp-theme($replace-me: not, $prop3: 2);`
);
expect(appTree.readContent("test.component.scss")).toEqual(
`igx-theme-func($replaced: 10px, $old-prop: 3, $prop3: 2);`
);
done();
});
it("should replace imports", (done) => {
const importsJson: ImportsChanges = {
changes: [
{
name: "IgxIconModule.forRoot()",
replaceWith: "IgxIconModule",
},
{
name: "module1",
replaceWith: "module2",
},
],
};
const jsonPath = path.join(__dirname, "changes", "imports.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(importsJson)
);
const fileContent = `
@NgModule({
declarations: components,
imports: [
IgxIconModule.forRoot(),
IgxGridModule,
IgxTreeGridModule,
module1
],
providers: [
LocalService,
],
bootstrap: [AppComponent]
})
export class AppModule { }`;
appTree.create("app.module.ts", fileContent);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getImportsChanges()).toEqual(importsJson);
update.applyChanges();
expect(appTree.readContent("app.module.ts")).toEqual(`
@NgModule({
declarations: components,
imports: [
IgxIconModule,
IgxGridModule,
IgxTreeGridModule,
module2
],
providers: [
LocalService,
],
bootstrap: [AppComponent]
})
export class AppModule { }`);
done();
});
it("should handle changes with valueTransform functions", (done) => {
const inputsJson: BindingChanges = {
changes: [
{
name: "someProp",
replaceWith: "someOtherProp",
valueTransform: "some_prop_transform",
owner: {
selector: "igx-component",
type: ElementType.Component,
},
},
],
};
const jsonPath = path.join(__dirname, "changes", "inputs.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn(fs, "readFileSync").and.returnValue(JSON.stringify(inputsJson));
// bracketed
appTree.create(
"test.component.html",
'<igx-component [someProp]="true"></igx-component>'
);
// No brackets
appTree.create(
"test2.component.html",
'<igx-component someProp="otherVal"></igx-component>'
);
// Small quotes
appTree.create(
"test3.component.html",
`<igx-component someProp='otherVal'></igx-component>`
);
// Multiple occurances
appTree.create(
"test4.component.html",
`<igx-component [someProp]="true"><igx-component>
<igx-component [someProp]="false" [someProp]="false" [someProp]="false" [someProp]="false"><igx-component>
<igx-component someProp="true"><igx-component>
<igx-component someProp="false"><igx-component>`
);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getInputChanges()).toEqual(inputsJson);
update.addValueTransform(
"some_prop_transform",
(args: BoundPropertyObject): void => {
if (args.bindingType === InputPropertyType.EVAL) {
args.value =
args.value === "true" ? "'trueValue'" : "'falseValue'";
} else {
args.value =
args.value === "true" ? "trueValue" : "falseValue";
}
}
);
update.applyChanges();
expect(appTree.readContent("test.component.html")).toEqual(
`<igx-component [someOtherProp]="'trueValue'"></igx-component>`
);
expect(appTree.readContent("test2.component.html")).toEqual(
`<igx-component someOtherProp="falseValue"></igx-component>`
);
expect(appTree.readContent("test3.component.html")).toEqual(
`<igx-component someOtherProp='falseValue'></igx-component>`
);
expect(appTree.readContent("test4.component.html")).toEqual(
`<igx-component [someOtherProp]="'trueValue'"><igx-component>\n` +
// eslint-disable-next-line max-len
`<igx-component [someOtherProp]="'falseValue'" [someOtherProp]="'falseValue'" [someOtherProp]="'falseValue'" [someOtherProp]="'falseValue'"><igx-component>
<igx-component someOtherProp="trueValue"><igx-component>
<igx-component someOtherProp="falseValue"><igx-component>`
);
done();
});
it("Should be able to change binding type via transform function", (done) => {
const inputsJson: BindingChanges = {
changes: [
{
name: "prop",
replaceWith: "newProp",
valueTransform: "prop_transform",
owner: {
selector: "igx-component",
type: ElementType.Component,
},
},
],
};
const jsonPath = path.join(__dirname, "changes", "inputs.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn(fs, "readFileSync").and.returnValue(JSON.stringify(inputsJson));
// bracketed
appTree.create(
"test-bound-to-string.component.html",
`<igx-component [prop]="true">STRING</igx-component>
<igx-component [prop]="false">STRING</igx-component>
<igx-component [prop]="someOtherProperty">BOUND</igx-component>`
);
appTree.create(
"test-string-to-bound.component.html",
`<igx-component prop="changeThisToBound">BOUND</igx-component>
<igx-component prop="leaveMeBe">STRING</igx-component>`
);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(fs.existsSync).toHaveBeenCalledWith(jsonPath);
expect(fs.readFileSync).toHaveBeenCalledWith(jsonPath, "utf-8");
expect(update.getInputChanges()).toEqual(inputsJson);
update.addValueTransform(
"prop_transform",
(args: BoundPropertyObject): void => {
if (args.bindingType === InputPropertyType.EVAL) {
switch (args.value) {
case "true":
args.value = "TRUTHY-STRING-VALUE";
args.bindingType = InputPropertyType.STRING;
break;
case "false":
args.value = "FALSY-STRING-VALUE";
args.bindingType = InputPropertyType.STRING;
break;
default:
args.value += " ? true : false";
}
} else {
if (args.value === "changeThisToBound") {
args.bindingType = InputPropertyType.EVAL;
args.value = "true";
}
}
}
);
update.applyChanges();
expect(
appTree.readContent("test-bound-to-string.component.html")
).toEqual(
`<igx-component newProp="TRUTHY-STRING-VALUE">STRING</igx-component>
<igx-component newProp="FALSY-STRING-VALUE">STRING</igx-component>
<igx-component [newProp]="someOtherProperty ? true : false">BOUND</igx-component>`
);
expect(
appTree.readContent("test-string-to-bound.component.html")
).toEqual(
`<igx-component [newProp]="true">BOUND</igx-component>
<igx-component newProp="leaveMeBe">STRING</igx-component>`
);
done();
});
describe("Project loading", () => {
it("should correctly load project files", () => {
const tsFile = "/src/component.ts";
const htmlFile = "/src/component.html";
const scssFile = "/src/component.scss";
const sassFile = "/src/component.sass";
appTree.overwrite(
"/angular.json",
JSON.stringify({
projects: {
testProj: {
sourceRoot: "/src",
},
},
})
);
appTree.create(tsFile, "");
appTree.create(htmlFile, "");
appTree.create(scssFile, "");
appTree.create(sassFile, "");
// skip loading json config files
spyOn(fs, "existsSync").and.returnValue(false);
const update = new UnitUpdateChanges(__dirname, appTree);
expect(update.tsFiles).toContain(tsFile);
expect(update.templateFiles).toContain(htmlFile);
expect(update.sassFiles).toContain(scssFile);
expect(update.sassFiles).toContain(sassFile);
});
it("should correctly load multiple and library project files", () => {
const tsFile = "component.ts";
const htmlFile = "component.html";
const scssFile = "component.scss";
const sassFile = "component.sass";
const workspace = {
projects: {
testProj: {
projectType: "application",
sourceRoot: "src-one",
},
test2Proj: {
projectType: "application",
sourceRoot: "/src-two",
},
libProj: {
projectType: "library",
sourceRoot: "src-lib",
},
},
};
appTree.overwrite("/angular.json", JSON.stringify(workspace));
for (const projName of Object.keys(workspace.projects)) {
const root = workspace.projects[projName].sourceRoot;
appTree.create(path.posix.join(root, tsFile), "");
appTree.create(path.posix.join(root, htmlFile), "");
appTree.create(path.posix.join(root, scssFile), "");
appTree.create(path.posix.join(root, sassFile), "");
}
// skip loading json config files
spyOn(fs, "existsSync").and.returnValue(false);
const update = new UnitUpdateChanges(__dirname, appTree);
for (const projName of Object.keys(workspace.projects)) {
const root = workspace.projects[projName].sourceRoot;
expect(update.tsFiles).toContain(
path.posix.join(`/${root}`, tsFile)
);
expect(update.templateFiles).toContain(
path.posix.join(`/${root}`, htmlFile)
);
expect(update.sassFiles).toContain(
path.posix.join(`/${root}`, scssFile)
);
expect(update.sassFiles).toContain(
path.posix.join(`/${root}`, sassFile)
);
}
});
});
describe("Language Service migrations", () => {
it("Should be able to replace property of an event", () => {
pending("set up tests for migrations through lang service");
const fileContent = `import { Component } from '@angular/core';
import { IGridCreatedEventArgs } from 'igniteui-angular-sovn';
@Component({
selector: 'app-custom-grid',
template: ''
})
export class CustomGridComponent {
public childGridCreated(event: IGridCreatedEventArgs) {
event.grid.onGridKeydown.subscribe(() => {});
}
}
`;
appTree.create("test.component.ts", fileContent);
const expectedFileContent = `import { Component } from '@angular/core';
import { IGridCreatedEventArgs } from 'igniteui-angular-sovn';
@Component({
selector: 'app-custom-grid',
template: ''
})
export class CustomGridComponent {
public childGridCreated(event: IGridCreatedEventArgs) {
event.grid.gridKeydown.subscribe(() => {});
}
}
`;
const update = new UnitUpdateChanges(__dirname, appTree);
update.applyChanges();
expect(appTree.readContent("test.component.ts")).toEqual(
expectedFileContent
);
});
});
it("Should migrate sass variables names correctly", () => {
const themeChangesJson: ThemeChanges = {
changes: [
{
name: "$light-material-palette",
replaceWith: "$igx-light-material-palette",
type: ThemeType.Variable,
},
{
name: "$light-palette",
replaceWith: "$igx-light-palette",
type: ThemeType.Variable,
},
{
name: "$dark-palette",
replaceWith: "$igx-dark-palette",
type: ThemeType.Variable,
},
{
name: "$color",
replaceWith: "$igx-color",
type: ThemeType.Variable,
},
{
name: "$elevation",
replaceWith: "$igx-elevation",
type: ThemeType.Variable,
},
],
};
const jsonPath = path.join(__dirname, "changes", "theme-changes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(themeChangesJson)
);
const fileContent = `$palette: $light-material-palette;
$light-material-palette : $some-variable,
$palette2: $light-material-palette-primary;
$palette3: mat.define-light-theme($light-palette-primary, $elevation, $light-palette);
$palette4: $elevation;
igx-dark-theme($light-palette, $elevation);
igx-white-theme($ease-in-quad, $pick-mint: $elevation);
igx-gray-theme($quick-draw: igx-icon($elevation));
igx-yellow-theme($some-property igx-icon($another-property, $elevation));
$color: #3321;
$colorful: #3245;
$palette5: mat.define-light-theme($light-palette-primary, $elevation, $light-palette, $some-variable: $elevation);
$crm-grid-theme: igx-grid-theme($elevation: #f0f8fe, $header-border-color: #dde5eb);
$dark-theme-palette: igx-palette($primary: $dark-color, $secondary: $orange-color);
$dark-grid-theme: igx-grid-theme(
$palette: $dark-theme-palette,
$content-background: igx-color($dark-theme-palette, "secondary", 100),
$elevation: igx-color($dark-theme-palette, "primary", 500),
$header-text-color: igx-color($dark-theme-palette, "secondary", 600),
$cell-selected-background: igx-color($dark-theme-palette, "primary", 500),
$cell-selected-text-color: igx-color($dark-theme-palette, "secondary", 500),
$row-hover-background: igx-color($dark-theme-palette, "primary", 100),
$header-border-color: igx-color($dark-theme-palette, "primary", 600)
);
`;
appTree.create("test.component.scss", fileContent);
const expectedFileContent = `$palette: $igx-light-material-palette;
$igx-light-material-palette : $some-variable,
$palette2: $light-material-palette-primary;
$palette3: mat.define-light-theme($light-palette-primary, $igx-elevation, $igx-light-palette);
$palette4: $igx-elevation;
igx-dark-theme($igx-light-palette, $igx-elevation);
igx-white-theme($ease-in-quad, $pick-mint: $igx-elevation);
igx-gray-theme($quick-draw: igx-icon($igx-elevation));
igx-yellow-theme($some-property igx-icon($another-property, $igx-elevation));
$igx-color: #3321;
$colorful: #3245;
$palette5: mat.define-light-theme($light-palette-primary, $igx-elevation, $igx-light-palette, $some-variable: $igx-elevation);
$crm-grid-theme: igx-grid-theme($elevation: #f0f8fe, $header-border-color: #dde5eb);
$dark-theme-palette: igx-palette($primary: $dark-color, $secondary: $orange-color);
$dark-grid-theme: igx-grid-theme(
$palette: $dark-theme-palette,
$content-background: igx-color($dark-theme-palette, "secondary", 100),
$elevation: igx-color($dark-theme-palette, "primary", 500),
$header-text-color: igx-color($dark-theme-palette, "secondary", 600),
$cell-selected-background: igx-color($dark-theme-palette, "primary", 500),
$cell-selected-text-color: igx-color($dark-theme-palette, "secondary", 500),
$row-hover-background: igx-color($dark-theme-palette, "primary", 100),
$header-border-color: igx-color($dark-theme-palette, "primary", 600)
);
`;
const update = new UnitUpdateChanges(__dirname, appTree);
update.applyChanges();
expect(appTree.readContent("test.component.scss")).toEqual(
expectedFileContent
);
});
it("Should migrate aliased scss functions", () => {
const themeChangesJson: ThemeChanges = {
changes: [
{
name: "igx-elevations",
replaceWith: "elevations",
type: ThemeType.Function,
},
{
name: "igx-contrast-color",
replaceWith: "contrast-color",
type: ThemeType.Function,
},
{
name: "igx-color",
replaceWith: "color",
type: ThemeType.Function,
},
{
name: "igx-palette",
replaceWith: "palette",
type: ThemeType.Function,
},
],
};
const jsonPath = path.join(__dirname, "changes", "theme-changes.json");
spyOn(fs, "existsSync").and.callFake((filePath: fs.PathLike) => {
if (filePath === jsonPath) {
return true;
}
return false;
});
spyOn<any>(fs, "readFileSync").and.callFake(() =>
JSON.stringify(themeChangesJson)
);
const fileContent = `@use 'igniteui-angular-sovn/theming' as igniteui1;
@use 'igniteui-angular-sovn/theme' as igniteui2;
@use 'igniteui-angular-sovn/lib/core/styles/themes/index' as igniteui3;
@use 'some/url' as my-namespace;
$my-palette: igniteui1.igx-palette($primary: red, $secondary: blue);
$my-text: igniteui3.text-contrast($background: yellow);
$my-color: igniteui2.igx-contrast-color($primary: blue, $secondary: orange);
$my-other-color: my-namespace.contrast-color($primary: white, $secondary: black, $elevation: true);
$my-other-theme: my-namespace.function1($color1: igniteui2.igx-contrast-color($palette: igniteui1.igx-palette($primary: blue, $secondary: white), $color: igniteui1.igx-color($palette: $my-palette, $color: $my-color)));
`;
appTree.create("test.component.scss", fileContent);
const expectedFileContent = `@use 'igniteui-angular-sovn/theming' as igniteui1;
@use 'igniteui-angular-sovn/theme' as igniteui2;
@use 'igniteui-angular-sovn/lib/core/styles/themes/index' as igniteui3;
@use 'some/url' as my-namespace;
$my-palette: igniteui1.palette($primary: red, $secondary: blue);
$my-text: igniteui3.text-contrast($background: yellow);
$my-color: igniteui2.contrast-color($primary: blue, $secondary: orange);
$my-other-color: my-namespace.contrast-color($primary: white, $secondary: black, $elevation: true);
$my-other-theme: my-namespace.function1($color1: igniteui2.contrast-color($palette: igniteui1.palette($primary: blue, $secondary: white), $color: igniteui1.color($palette: $my-palette, $color: $my-color)));
`;
const update = new UnitUpdateChanges(__dirname, appTree);
update.applyChanges();
expect(appTree.readContent("test.component.scss")).t