Skip to content

Commit eac2309

Browse files
Ivan KitanovIvan Kitanov
authored andcommitted
chore(simple-combo): Adding migrations for disableFiltering
1 parent 05c226c commit eac2309

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

projects/igniteui-angular/migrations/migration-collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@
236236
"version": "20.0.2",
237237
"description": "Updates Ignite UI for Angular from v20.0.0 to v20.0.2",
238238
"factory": "./update-20_0_2"
239+
},
240+
"migration-48": {
241+
"version": "20.0.5",
242+
"description": "Updates Ignite UI for Angular from v20.0.2 to v20.0.5",
243+
"factory": "./update-20_0_5"
239244
}
240245
}
241246
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "../../common/schema/binding.schema.json",
3+
"changes": [
4+
{
5+
"name": "filteringOptions.filterable",
6+
"replaceWith": "disableFiltering",
7+
"owner": {
8+
"selector": "igx-simple-combo",
9+
"type": "component"
10+
}
11+
}
12+
]
13+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as path from 'path';
2+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
3+
import { setupTestTree } from '../common/setup.spec';
4+
5+
describe('Migration 20.0.5 - Replace filteringOptions.filterable', () => {
6+
let appTree: UnitTestTree;
7+
const runner = new SchematicTestRunner(
8+
'ig-migrate',
9+
path.join(__dirname, '../migration-collection.json')
10+
);
11+
const migrationName = 'migration-48';
12+
const makeTemplate = (name: string) => `/testSrc/appPrefix/component/${name}.component.html`;
13+
14+
beforeEach(() => {
15+
appTree = setupTestTree();
16+
});
17+
18+
it('should replace simple inline filteringOptions', async () => {
19+
const input = `<igx-simple-combo [filteringOptions]="{ filterable: true }"></igx-simple-combo>`;
20+
appTree.create(makeTemplate('inline1'), input);
21+
22+
const tree = await runner.runSchematic(migrationName, {}, appTree);
23+
const output = tree.readContent(makeTemplate('inline1'));
24+
25+
expect(output).toContain(`[disableFiltering]="false"`);
26+
expect(output).not.toContain('filterable');
27+
});
28+
29+
it('should handle mixed object literal correctly', async () => {
30+
const input = `<igx-simple-combo [filteringOptions]="{ filterable: false, caseSensitive: true }"></igx-simple-combo>`;
31+
appTree.create(makeTemplate('inline2'), input);
32+
33+
const tree = await runner.runSchematic(migrationName, {}, appTree);
34+
const output = tree.readContent(makeTemplate('inline2'));
35+
36+
expect(output).toContain(`[disableFiltering]="true"`);
37+
expect(output).toContain(`[filteringOptions]="{ caseSensitive: true }"`);
38+
});
39+
40+
it('should warn on variable reference', async () => {
41+
const input = `<igx-simple-combo [filteringOptions]="filterOpts"></igx-simple-combo>`;
42+
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
43+
"If you were using filteringOptions please include them without 'filterable'";
44+
45+
appTree.create(makeTemplate('referenceInTsFile'), input);
46+
47+
const tree = await runner.runSchematic(migrationName, {}, appTree);
48+
const output = tree.readContent(makeTemplate('referenceInTsFile'));
49+
50+
expect(output).not.toContain('[filteringOptions]');
51+
expect(output).toContain(warnMsg);
52+
});
53+
});
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { Element } from '@angular/compiler';
2+
import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
3+
import { UpdateChanges } from '../common/UpdateChanges';
4+
import {
5+
FileChange,
6+
findElementNodes,
7+
getSourceOffset,
8+
parseFile
9+
} from '../common/util';
10+
import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.js';
11+
12+
const version = '20.0.5';
13+
14+
export default (): Rule => async (host: Tree, context: SchematicContext) => {
15+
context.logger.info(
16+
`Applying migration for Ignite UI for Angular to version ${version}`
17+
);
18+
19+
const { HtmlParser } = await nativeImport('@angular/compiler') as typeof import('@angular/compiler');
20+
21+
const update = new UpdateChanges(__dirname, host, context);
22+
const changes = new Map<string, FileChange[]>();
23+
const parser = new HtmlParser();
24+
25+
const warnMsg = "Manual migration needed: please use 'disableFiltering' instead of filteringOptions.filterable." +
26+
"If you were using filteringOptions please include them without 'filterable'";
27+
28+
const applyChanges = () => {
29+
for (const [path, fileChanges] of changes.entries()) {
30+
let content = host.read(path).toString();
31+
fileChanges.sort((a, b) => b.position - a.position).forEach(c => {
32+
content = c.apply(content);
33+
});
34+
host.overwrite(path, content);
35+
}
36+
};
37+
38+
const addChange = (path: string, change: FileChange) => {
39+
if (!changes.has(path)) {
40+
changes.set(path, []);
41+
}
42+
changes.get(path).push(change);
43+
};
44+
45+
const COMBO_TAG = 'igx-simple-combo';
46+
47+
for (const path of update.templateFiles) {
48+
const nodes = findElementNodes(parseFile(parser, host, path), COMBO_TAG);
49+
50+
for (const node of nodes) {
51+
if (!(node instanceof Element)) continue;
52+
53+
const attr = node.attrs.find(a => a.name === '[filteringOptions]');
54+
if (!attr) continue;
55+
56+
const attrVal = attr.value.trim();
57+
const offset = getSourceOffset(node);
58+
const file = offset.file;
59+
60+
let replacementText = '';
61+
62+
if (attrVal.startsWith('{')) {
63+
// inline object literal
64+
const parsed = eval('(' + attrVal + ')');
65+
const filterable = parsed.filterable;
66+
67+
if (typeof filterable === 'boolean') {
68+
replacementText += `[disableFiltering]="${!filterable}"`;
69+
}
70+
71+
const remaining = { ...parsed };
72+
delete remaining.filterable;
73+
const remainingProps = Object.entries(remaining)
74+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
75+
.join(', ');
76+
77+
if (remainingProps.length > 0) {
78+
replacementText += ` [filteringOptions]="{ ${remainingProps} }"`;
79+
}
80+
81+
// Replace whole [filteringOptions] attribute
82+
const match = node.sourceSpan.toString().match(/\[filteringOptions\]="([^"]+)"/);
83+
if (match) {
84+
const attrText = match[0];
85+
const attrPos = file.content.indexOf(attrText, offset.startTag.start);
86+
addChange(file.url, new FileChange(attrPos, replacementText, attrText, 'replace'));
87+
}
88+
} else {
89+
// log for manual TS edit
90+
const attrText = `[filteringOptions]="${attrVal}"`;
91+
const attrPos = file.content.indexOf(attrText, offset.startTag.start);
92+
93+
addChange(file.url, new FileChange(attrPos, '', attrText, 'replace'));
94+
addChange(file.url, new FileChange(offset.startTag.end, warnMsg));
95+
}
96+
}
97+
}
98+
99+
applyChanges();
100+
101+
update.applyChanges();
102+
};

0 commit comments

Comments
 (0)