From be6f36be5d1c83ce7636cc05a396b7b6289783dd Mon Sep 17 00:00:00 2001 From: "INFRAGISTICS\\IPetrov" Date: Tue, 20 Oct 2026 16:22:30 +0300 Subject: [PATCH 1/2] fix(*): fix doc command, custom grid templates and config error messages - doc: pass the search url to open() instead of as `this` - hierarchical-grid-custom: add the toolbar once and reset state between calls - grid-custom: do not enable grid filtering when only sorting is selected - multi-column-headers: drop the no-op "Column Pinning" option and use grid-level [moving] instead of the removed igx-column [movable] input - ng-schematics: report a missing angular.json with the current devkit error - config: drop the undefined schema title from the unknown property message --- packages/cli/lib/commands/doc.ts | 2 +- packages/core/util/ProjectConfig.ts | 2 +- .../igx-ts/grid/grid-custom/index.ts | 2 +- .../igx-ts/grid/multi-column-headers/index.ts | 6 +- .../hierarchical-grid-custom/index.ts | 103 +++++++++--------- .../src/utils/cli-config.spec.ts | 23 ++++ .../ng-schematics/src/utils/cli-config.ts | 2 +- spec/helpers/utils.ts | 11 ++ spec/unit/ProjectConfig-spec.ts | 8 ++ spec/unit/doc-spec.ts | 14 +++ spec/unit/igx-templates/grid-custom-spec.ts | 39 +++++++ .../hierarchical-grid-custom-spec.ts | 55 ++++++++++ .../multi-column-headers-spec.ts | 38 +++++++ 13 files changed, 249 insertions(+), 56 deletions(-) create mode 100644 packages/ng-schematics/src/utils/cli-config.spec.ts create mode 100644 spec/unit/igx-templates/grid-custom-spec.ts create mode 100644 spec/unit/igx-templates/hierarchical-grid-custom-spec.ts create mode 100644 spec/unit/igx-templates/multi-column-headers-spec.ts diff --git a/packages/cli/lib/commands/doc.ts b/packages/cli/lib/commands/doc.ts index cef33ff543..98cc56a6c2 100644 --- a/packages/cli/lib/commands/doc.ts +++ b/packages/cli/lib/commands/doc.ts @@ -15,7 +15,7 @@ const doc: DocCommandType = { }, open: async (target) => { const open = await import("open"); - open.default.call(target); + await open.default(target); }, async handler(argv: ArgumentsCamelCase) { GoogleAnalytics.post({ diff --git a/packages/core/util/ProjectConfig.ts b/packages/core/util/ProjectConfig.ts index fd07d39f35..e07f27a2d9 100644 --- a/packages/core/util/ProjectConfig.ts +++ b/packages/core/util/ProjectConfig.ts @@ -91,7 +91,7 @@ export class ProjectConfig { const result = { message: undefined, valid: false, value: undefined }; if (!schema.properties.hasOwnProperty(property)) { - result.message = `Property "${property}" is not allowed in "${schema.title}" type!`; + result.message = `Property "${property}" is not allowed in the Ignite UI CLI configuration!`; return result; } diff --git a/packages/igx-templates/igx-ts/grid/grid-custom/index.ts b/packages/igx-templates/igx-ts/grid/grid-custom/index.ts index 778733801e..532cca9f82 100644 --- a/packages/igx-templates/igx-ts/grid/grid-custom/index.ts +++ b/packages/igx-templates/igx-ts/grid/grid-custom/index.ts @@ -81,9 +81,9 @@ class IgxCustomGridTemplate extends IgniteUIForAngularTemplate { const featuresUrls = []; for (const feature of this.userExtraConfiguration["columnFeatures"] as string[]) { switch (feature) { - case "Sorting": case "Filtering": gridFeatures.push(`[allowFiltering]="true"`); + case "Sorting": case "Resizing": const text = `[${feature.toLowerCase().replace("ing", "able")}]="true"`; columnFeatures.push(text); diff --git a/packages/igx-templates/igx-ts/grid/multi-column-headers/index.ts b/packages/igx-templates/igx-ts/grid/multi-column-headers/index.ts index 07a82df2bf..38fa04d834 100644 --- a/packages/igx-templates/igx-ts/grid/multi-column-headers/index.ts +++ b/packages/igx-templates/igx-ts/grid/multi-column-headers/index.ts @@ -24,7 +24,7 @@ class IgxMultiColumnHeadersTemplate extends IgniteUIForAngularTemplate { public getExtraConfiguration(): ControlExtraConfiguration[] { return [{ - choices: ["Sorting", "Filtering", "Resizing", "Paging", "Column Pinning", "Moving"], + choices: ["Sorting", "Filtering", "Resizing", "Paging", "Moving"], default: "", key: "columnFeatures", message: "Choose features for the igx-grid", @@ -49,11 +49,13 @@ class IgxMultiColumnHeadersTemplate extends IgniteUIForAngularTemplate { case "Sorting": case "Filtering": case "Resizing": - case "Moving": const text = `[${feature.toLowerCase().replace("ing", "able")}]="true"`; columnFeatures.push(text); columnBoolFeatures.push(text); break; + case "Moving": + gridFeatures.push(`[moving]="true"`); + break; case "Row Selection": const gridFeatureText = `rowSelection="multiple"`; gridFeatures.push(gridFeatureText); diff --git a/packages/igx-templates/igx-ts/hierarchical-grid/hierarchical-grid-custom/index.ts b/packages/igx-templates/igx-ts/hierarchical-grid/hierarchical-grid-custom/index.ts index 1cd53d2e91..683cd621fa 100644 --- a/packages/igx-templates/igx-ts/hierarchical-grid/hierarchical-grid-custom/index.ts +++ b/packages/igx-templates/igx-ts/hierarchical-grid/hierarchical-grid-custom/index.ts @@ -48,6 +48,9 @@ class IgxHierarchicalGridTemplate extends IgniteUIForAngularTemplate { } public generateConfig(name: string, ...options: any[]): {[key: string]: any} { + // the template instance is shared, so reset state left over from previous calls + this.usePinning = false; + this.additionalElements = [""]; const columnFeatures = []; const gridFeatures = []; const extraConfig = { @@ -139,57 +142,57 @@ class IgxHierarchicalGridTemplate extends IgniteUIForAngularTemplate { } featuresUrls.push(this.getFeatureUrl(feature, featureUrl)); - selectedFeatures = features.map((e, i) => { - anchorWrapper.href = featuresUrls[i]; - anchorWrapper.text = e; - return ` ${anchorWrapper.start}${anchorWrapper.href}${anchorWrapper.middle}` + - `${anchorWrapper.text}${anchorWrapper.end}`; - }).toString(); - if (selectedFeatures.length > 0) { - selectedFeatures = `

Active Features: ${selectedFeatures}

`; - } - if (toolbarActions.length) { - this.dependencies = [ - ...this.dependencies, - ...[ - { - standalone: true, - import: "IgxGridToolbarComponent", - from: "<%=igxPackage%>" - }, - { - standalone: true, - import: "IgxGridToolbarTitleComponent", - from: "<%=igxPackage%>" - }, - { - standalone: true, - import: "IgxGridToolbarActionsComponent", - from: "<%=igxPackage%>" - }, - { - standalone: true, - import: "IgxGridToolbarPinningComponent", - from: "<%=igxPackage%>" - }, - { - standalone: true, - import: "IgxGridToolbarHidingComponent", - from: "<%=igxPackage%>" - }, - ], - ]; + } + selectedFeatures = features.map((e, i) => { + anchorWrapper.href = featuresUrls[i]; + anchorWrapper.text = e; + return ` ${anchorWrapper.start}${anchorWrapper.href}${anchorWrapper.middle}` + + `${anchorWrapper.text}${anchorWrapper.end}`; + }).toString(); + if (selectedFeatures.length > 0) { + selectedFeatures = `

Active Features: ${selectedFeatures}

`; + } + if (toolbarActions.length) { + this.dependencies = [ + ...this.dependencies, + ...[ + { + standalone: true, + import: "IgxGridToolbarComponent", + from: "<%=igxPackage%>" + }, + { + standalone: true, + import: "IgxGridToolbarTitleComponent", + from: "<%=igxPackage%>" + }, + { + standalone: true, + import: "IgxGridToolbarActionsComponent", + from: "<%=igxPackage%>" + }, + { + standalone: true, + import: "IgxGridToolbarPinningComponent", + from: "<%=igxPackage%>" + }, + { + standalone: true, + import: "IgxGridToolbarHidingComponent", + from: "<%=igxPackage%>" + }, + ], + ]; - const parts = [ - " ", - " Singers", - " ", - ...toolbarActions, - " ", - " " - ]; - this.additionalElements.splice(1, 0, parts.join("\n")); - } + const parts = [ + " ", + " Singers", + " ", + ...toolbarActions, + " ", + " " + ]; + this.additionalElements.splice(1, 0, parts.join("\n")); } } diff --git a/packages/ng-schematics/src/utils/cli-config.spec.ts b/packages/ng-schematics/src/utils/cli-config.spec.ts new file mode 100644 index 0000000000..aa161a3a28 --- /dev/null +++ b/packages/ng-schematics/src/utils/cli-config.spec.ts @@ -0,0 +1,23 @@ +import { EmptyTree, SchematicContext, SchematicsException } from "@angular-devkit/schematics"; +import { UnitTestTree } from "@angular-devkit/schematics/testing"; +import { createCliConfig } from "./cli-config"; + +describe("createCliConfig", () => { + let tree: UnitTestTree; + let context: SchematicContext; + + beforeEach(() => { + tree = new UnitTestTree(new EmptyTree()); + context = { + logger: jasmine.createSpyObj("logger", ["info", "warn"]) + } as unknown as SchematicContext; + }); + + it("should report a missing angular.json", async () => { + const rule = createCliConfig() as (tree: UnitTestTree, context: SchematicContext) => Promise; + + await expectAsync(rule(tree, context)) + .toBeRejectedWithError(SchematicsException, "angular.json was not found in the project's root"); + expect(tree.exists("ignite-ui-cli.json")).toBeFalse(); + }); +}); diff --git a/packages/ng-schematics/src/utils/cli-config.ts b/packages/ng-schematics/src/utils/cli-config.ts index 4df8e5ea09..aa089e47aa 100644 --- a/packages/ng-schematics/src/utils/cli-config.ts +++ b/packages/ng-schematics/src/utils/cli-config.ts @@ -23,7 +23,7 @@ async function GetCliConfig(tree: Tree): Promise { try { ({ workspace } = await workspaces.readWorkspace("/", createWorkspaceHost(tree))); } catch (e) { - if (e.toString().includes("Could not find (undefined)")) { + if (e.toString().includes("Unable to determine format for workspace path")) { throw new SchematicsException("angular.json was not found in the project's root"); } throw new Error(e.message); diff --git a/spec/helpers/utils.ts b/spec/helpers/utils.ts index 301a9f2ed3..c7531705d9 100644 --- a/spec/helpers/utils.ts +++ b/spec/helpers/utils.ts @@ -47,3 +47,14 @@ export function filesDiff( export function resetSpy(spy: any) { (spy as jasmine.Spy).calls.reset(); } + +/** + * Loads a fresh copy of a module, bypassing the require cache. + * Use for modules that export a stateful singleton, like template instances. + * @param modulePath Absolute path to the module + */ +export function requireFresh(modulePath: string): T { + const resolved = require.resolve(modulePath); + delete require.cache[resolved]; + return require(resolved); +} diff --git a/spec/unit/ProjectConfig-spec.ts b/spec/unit/ProjectConfig-spec.ts index 5bea463156..360b5358e6 100644 --- a/spec/unit/ProjectConfig-spec.ts +++ b/spec/unit/ProjectConfig-spec.ts @@ -43,4 +43,12 @@ describe("Unit - ProjectConfig", () => { expect(ProjectConfig.hasLocalConfig()).toBeTruthy(); expect(fs.statSync).toHaveBeenCalledTimes(1); }); + + it("validateProperty rejects properties that are not in the schema", () => { + const result = ProjectConfig.validateProperty("notAConfigProperty", "value"); + expect(result.valid).toBeFalse(); + expect(result.value).toBeUndefined(); + expect(result.message) + .toBe(`Property "notAConfigProperty" is not allowed in the Ignite UI CLI configuration!`); + }); }); diff --git a/spec/unit/doc-spec.ts b/spec/unit/doc-spec.ts index e96f0e4c36..35821a8753 100644 --- a/spec/unit/doc-spec.ts +++ b/spec/unit/doc-spec.ts @@ -42,4 +42,18 @@ describe("Unit - Doc command", () => { expect(Util.error).toHaveBeenCalledWith("The search term 'Th1s i$ incorrect' is not valid." + "\n" + "Name should start with a letter and can also contain numbers, dashes and spaces.", "red"); }); + it("Should pass the target url to the 'open' package", async () => { + const openSpy = jasmine.createSpy("open").and.returnValue(Promise.resolve()); + const moduleLoader = require("module"); + const originalLoad = moduleLoader._load; + spyOn(moduleLoader, "_load").and.callFake(function (request: string, ...args: unknown[]) { + if (request === "open") { + return { __esModule: true, default: openSpy }; + } + return originalLoad.call(this, request, ...args); + }); + await doc.open("https://www.infragistics.com/search?q=igGrid"); + expect(openSpy).toHaveBeenCalledTimes(1); + expect(openSpy).toHaveBeenCalledWith("https://www.infragistics.com/search?q=igGrid"); + }); }); diff --git a/spec/unit/igx-templates/grid-custom-spec.ts b/spec/unit/igx-templates/grid-custom-spec.ts new file mode 100644 index 0000000000..8c1da6f8ae --- /dev/null +++ b/spec/unit/igx-templates/grid-custom-spec.ts @@ -0,0 +1,39 @@ +import { App, Template } from "@igniteui/cli-core"; +import * as path from "path"; +import { requireFresh } from "../../helpers/utils"; + +const templatePath = path.join(__dirname, "../../../packages/igx-templates/igx-ts/grid/grid-custom"); + +describe("Unit - igx-ts Custom Grid template", () => { + let template: Template; + + beforeAll(() => { + App.initialize(); + }); + + beforeEach(() => { + // the template module exports a singleton that keeps state between calls + template = requireFresh