@seniorsistemas/angular-components 19.7.0 → 19.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dynamic-form/dynamic-form/components/lookup/lookup.component.d.ts +2 -1
- package/dynamic-form/dynamic-form/components/lookup/types/lookup-models.d.ts +2 -1
- package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +1 -1
- package/esm2022/dynamic-form/dynamic-form/components/lookup/types/lookup-models.mjs +1 -1
- package/esm2022/lib/locale/fallback.mjs +3 -1
- package/esm2022/table/lib/table/table-column/column-number-format.utils.mjs +24 -0
- package/esm2022/table/lib/table/table-paging/export-number-format.mjs +104 -0
- package/esm2022/table/lib/table/table-paging/models/export-number-format.enum.mjs +6 -0
- package/esm2022/table/lib/table/table-paging/models/index.mjs +2 -2
- package/esm2022/table/lib/table/table-paging/table-paging.component.mjs +58 -99
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-table.mjs +181 -98
- package/fesm2022/seniorsistemas-angular-components-table.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components.mjs +2 -0
- package/fesm2022/seniorsistemas-angular-components.mjs.map +1 -1
- package/package.json +31 -31
- package/schematics/migrations/migrate-dynamic-form-v19/index.d.ts +2 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.js +964 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.js.map +1 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.d.ts +1 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js +888 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js.map +1 -0
- package/schematics/migrations/migrate-p-datepicker/index.js +4 -8
- package/schematics/migrations/migrate-p-datepicker/index.js.map +1 -1
- package/schematics/migrations/migrate-p-editor/index.d.ts +2 -0
- package/schematics/migrations/migrate-p-editor/index.js +301 -0
- package/schematics/migrations/migrate-p-editor/index.js.map +1 -0
- package/schematics/migrations/migrate-p-editor/index.test.d.ts +1 -0
- package/schematics/migrations/migrate-p-editor/index.test.js +393 -0
- package/schematics/migrations/migrate-p-editor/index.test.js.map +1 -0
- package/schematics/migrations.json +11 -0
- package/schematics/utils/ast-utils.d.ts +7 -0
- package/schematics/utils/ast-utils.js +20 -0
- package/schematics/utils/ast-utils.js.map +1 -1
- package/schematics/utils/html-utils.d.ts +6 -0
- package/schematics/utils/html-utils.js +15 -0
- package/schematics/utils/html-utils.js.map +1 -0
- package/table/README.md +28 -0
- package/table/lib/table/table-column/column-number-format.utils.d.ts +5 -0
- package/table/lib/table/table-paging/export-number-format.d.ts +15 -0
- package/table/lib/table/table-paging/models/export-number-format.enum.d.ts +4 -0
- package/table/lib/table/table-paging/models/index.d.ts +1 -0
- package/table/lib/table/table-paging/table-paging.component.d.ts +10 -27
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
4
|
+
const testing_1 = require("@angular-devkit/schematics/testing");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const migrationsPath = path.join(__dirname, '../../migrations.json');
|
|
7
|
+
const runner = new testing_1.SchematicTestRunner('migrations', migrationsPath);
|
|
8
|
+
const MIGRATION = 'migration-v19-0-0-dynamic-form';
|
|
9
|
+
const DEFAULT_ANGULAR_JSON = JSON.stringify({
|
|
10
|
+
projects: {
|
|
11
|
+
app: {
|
|
12
|
+
architect: {
|
|
13
|
+
build: {
|
|
14
|
+
builder: '@angular-devkit/build-angular:application',
|
|
15
|
+
options: { styles: ['src/styles.scss'] },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const DEFAULT_PACKAGE_JSON = JSON.stringify({ dependencies: { quill: '^1.0.0' } }, null, 2) + '\n';
|
|
22
|
+
async function runMigration(files) {
|
|
23
|
+
const tree = new testing_1.UnitTestTree(schematics_1.Tree.empty());
|
|
24
|
+
if (!files['/angular.json'] && !files['angular.json']) {
|
|
25
|
+
tree.create('/angular.json', DEFAULT_ANGULAR_JSON);
|
|
26
|
+
}
|
|
27
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
28
|
+
tree.create(filePath, content);
|
|
29
|
+
}
|
|
30
|
+
return runner.runSchematic(MIGRATION, {}, tree);
|
|
31
|
+
}
|
|
32
|
+
// ─── Arquivos sem DynamicForm — não devem ser alterados ───────────────────────
|
|
33
|
+
describe('migrate-dynamic-form-v19 — arquivos sem dynamic-form', () => {
|
|
34
|
+
it('não altera arquivo sem keywords do DynamicForm', async () => {
|
|
35
|
+
const original = `import { Component } from '@angular/core';\n\n@Component({ selector: 'app-root', template: '' })\nexport class AppComponent {}\n`;
|
|
36
|
+
const result = await runMigration({ 'src/app/app.component.ts': original });
|
|
37
|
+
expect(result.readText('src/app/app.component.ts')).toBe(original);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// ─── A1: Enum → String literal ────────────────────────────────────────────────
|
|
41
|
+
describe('migrate-dynamic-form-v19 — A1: enum → string literal', () => {
|
|
42
|
+
it('converte FieldType.String → string', async () => {
|
|
43
|
+
const result = await runMigration({
|
|
44
|
+
'src/app/comp.ts': `
|
|
45
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
46
|
+
const f = new FormField({ type: FieldType.String, name: 'x' });
|
|
47
|
+
`,
|
|
48
|
+
});
|
|
49
|
+
expect(result.readText('src/app/comp.ts')).toContain("'string'");
|
|
50
|
+
expect(result.readText('src/app/comp.ts')).not.toContain('FieldType.String');
|
|
51
|
+
});
|
|
52
|
+
it('converte FieldType.Radio → radioButton', async () => {
|
|
53
|
+
const result = await runMigration({
|
|
54
|
+
'src/app/comp.ts': `
|
|
55
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
56
|
+
const f = new FormField({ type: FieldType.Radio, name: 'r' });
|
|
57
|
+
`,
|
|
58
|
+
});
|
|
59
|
+
expect(result.readText('src/app/comp.ts')).toContain("'radioButton'");
|
|
60
|
+
});
|
|
61
|
+
it('converte FieldType.Text → textArea', async () => {
|
|
62
|
+
const result = await runMigration({
|
|
63
|
+
'src/app/comp.ts': `
|
|
64
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
65
|
+
const f = new FormField({ type: FieldType.Text, name: 't' });
|
|
66
|
+
`,
|
|
67
|
+
});
|
|
68
|
+
expect(result.readText('src/app/comp.ts')).toContain("'textArea'");
|
|
69
|
+
});
|
|
70
|
+
it('converte FieldType.TextIA → contentGenerator', async () => {
|
|
71
|
+
const result = await runMigration({
|
|
72
|
+
'src/app/comp.ts': `
|
|
73
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
74
|
+
const f = new FormField({ type: FieldType.TextIA, name: 'ia' });
|
|
75
|
+
`,
|
|
76
|
+
});
|
|
77
|
+
expect(result.readText('src/app/comp.ts')).toContain("'contentGenerator'");
|
|
78
|
+
});
|
|
79
|
+
it('converte FieldType.Date → date', async () => {
|
|
80
|
+
const result = await runMigration({
|
|
81
|
+
'src/app/comp.ts': `
|
|
82
|
+
import { CalendarField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
83
|
+
const f = new CalendarField({ type: FieldType.Date, name: 'dt' });
|
|
84
|
+
`,
|
|
85
|
+
});
|
|
86
|
+
expect(result.readText('src/app/comp.ts')).toContain("'date'");
|
|
87
|
+
});
|
|
88
|
+
it('converte DynamicType.Row → row (enum v18)', async () => {
|
|
89
|
+
const result = await runMigration({
|
|
90
|
+
'src/app/comp.ts': `
|
|
91
|
+
import { DynamicType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
92
|
+
const cfg = [{ type: DynamicType.Row, fields: [] }];
|
|
93
|
+
`,
|
|
94
|
+
});
|
|
95
|
+
expect(result.readText('src/app/comp.ts')).toContain("'row'");
|
|
96
|
+
expect(result.readText('src/app/comp.ts')).not.toContain('DynamicType.Row');
|
|
97
|
+
});
|
|
98
|
+
it('converte DynamicType.Section → section (enum v18)', async () => {
|
|
99
|
+
const result = await runMigration({
|
|
100
|
+
'src/app/comp.ts': `
|
|
101
|
+
import { DynamicType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
102
|
+
const cfg = [{ type: DynamicType.Section, fields: [] }];
|
|
103
|
+
`,
|
|
104
|
+
});
|
|
105
|
+
expect(result.readText('src/app/comp.ts')).toContain("'section'");
|
|
106
|
+
});
|
|
107
|
+
it('converte GridType.Row → row', async () => {
|
|
108
|
+
const result = await runMigration({
|
|
109
|
+
'src/app/comp.ts': `
|
|
110
|
+
import { GridType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
111
|
+
const cfg = [{ type: GridType.Row, fields: [] }];
|
|
112
|
+
`,
|
|
113
|
+
});
|
|
114
|
+
expect(result.readText('src/app/comp.ts')).toContain("'row'");
|
|
115
|
+
expect(result.readText('src/app/comp.ts')).not.toContain('GridType.Row');
|
|
116
|
+
});
|
|
117
|
+
it('converte StructureType.Section → section', async () => {
|
|
118
|
+
const result = await runMigration({
|
|
119
|
+
'src/app/comp.ts': `
|
|
120
|
+
import { StructureType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
121
|
+
const cfg = [{ type: StructureType.Section, fields: [] }];
|
|
122
|
+
`,
|
|
123
|
+
});
|
|
124
|
+
expect(result.readText('src/app/comp.ts')).toContain("'section'");
|
|
125
|
+
expect(result.readText('src/app/comp.ts')).not.toContain('StructureType.Section');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ─── A2: Construtores → plain objects ─────────────────────────────────────────
|
|
129
|
+
describe('migrate-dynamic-form-v19 — A2: construtores → plain objects', () => {
|
|
130
|
+
it('converte new FormField({...}) → plain object', async () => {
|
|
131
|
+
const result = await runMigration({
|
|
132
|
+
'src/app/comp.ts': `
|
|
133
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
134
|
+
const f = new FormField({ type: FieldType.String, name: 'x', mask: '999' });
|
|
135
|
+
`,
|
|
136
|
+
});
|
|
137
|
+
const text = result.readText('src/app/comp.ts');
|
|
138
|
+
expect(text).not.toContain('new FormField');
|
|
139
|
+
expect(text).toContain("{ type: 'string', name: 'x', mask: '999' }");
|
|
140
|
+
});
|
|
141
|
+
it('converte new TextField({...}) → plain object em array', async () => {
|
|
142
|
+
const result = await runMigration({
|
|
143
|
+
'src/app/comp.ts': `
|
|
144
|
+
import { TextField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
145
|
+
const fields = [new TextField({ type: FieldType.String, name: 'n' })];
|
|
146
|
+
`,
|
|
147
|
+
});
|
|
148
|
+
const text = result.readText('src/app/comp.ts');
|
|
149
|
+
expect(text).not.toContain('new TextField');
|
|
150
|
+
expect(text).toContain("{ type: 'string', name: 'n' }");
|
|
151
|
+
expect(text).not.toContain('as TextFieldConfig'); // inside array, no cast needed
|
|
152
|
+
});
|
|
153
|
+
it('converte new CalendarField({...}) → plain object', async () => {
|
|
154
|
+
const result = await runMigration({
|
|
155
|
+
'src/app/comp.ts': `
|
|
156
|
+
import { CalendarField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
157
|
+
const fields = [new CalendarField({ type: FieldType.Date, name: 'dt', appendTo: 'body' })];
|
|
158
|
+
`,
|
|
159
|
+
});
|
|
160
|
+
const text = result.readText('src/app/comp.ts');
|
|
161
|
+
expect(text).not.toContain('new CalendarField');
|
|
162
|
+
expect(text).toContain("'date'");
|
|
163
|
+
expect(text).toContain("appendTo: 'body'");
|
|
164
|
+
});
|
|
165
|
+
it('adiciona type cast quando objeto não está em array (isolado)', async () => {
|
|
166
|
+
const result = await runMigration({
|
|
167
|
+
'src/app/comp.ts': `
|
|
168
|
+
import { TextField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
169
|
+
const f = new TextField({ type: FieldType.String, name: 'x' });
|
|
170
|
+
`,
|
|
171
|
+
});
|
|
172
|
+
const text = result.readText('src/app/comp.ts');
|
|
173
|
+
expect(text).toContain('as TextFieldConfig');
|
|
174
|
+
});
|
|
175
|
+
it('não adiciona type cast quando FormField está em array', async () => {
|
|
176
|
+
const result = await runMigration({
|
|
177
|
+
'src/app/comp.ts': `
|
|
178
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
179
|
+
const fields = [new FormField({ type: FieldType.String, name: 'x' })];
|
|
180
|
+
`,
|
|
181
|
+
});
|
|
182
|
+
const text = result.readText('src/app/comp.ts');
|
|
183
|
+
expect(text).not.toContain('as DynamicType');
|
|
184
|
+
expect(text).not.toContain('as FormField');
|
|
185
|
+
});
|
|
186
|
+
it('envolve em parênteses quando new XxxField({...}) é o corpo conciso de uma arrow function', async () => {
|
|
187
|
+
const result = await runMigration({
|
|
188
|
+
'src/app/comp.ts': `
|
|
189
|
+
import { TextField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
190
|
+
const creators = {
|
|
191
|
+
string: () => new TextField({ type: FieldType.String, name: 'x' }),
|
|
192
|
+
};
|
|
193
|
+
`,
|
|
194
|
+
});
|
|
195
|
+
const text = result.readText('src/app/comp.ts');
|
|
196
|
+
// a leading `{` right after `=>` would be parsed as a block, not an object literal —
|
|
197
|
+
// the parenthesized form below is the only syntactically valid rendering
|
|
198
|
+
expect(text).toContain("string: () => ({ type: 'string', name: 'x' } as TextFieldConfig),");
|
|
199
|
+
});
|
|
200
|
+
it('envolve em parênteses quando new XxxField({...}) é uma expression statement isolada', async () => {
|
|
201
|
+
const result = await runMigration({
|
|
202
|
+
'src/app/comp.ts': `
|
|
203
|
+
import { TextField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
204
|
+
new TextField({ type: FieldType.String, name: 'x' });
|
|
205
|
+
`,
|
|
206
|
+
});
|
|
207
|
+
const text = result.readText('src/app/comp.ts');
|
|
208
|
+
expect(text).toContain("({ type: 'string', name: 'x' } as TextFieldConfig);");
|
|
209
|
+
});
|
|
210
|
+
it('importa a interface XxxFieldConfig usada em cast isolado', async () => {
|
|
211
|
+
const result = await runMigration({
|
|
212
|
+
'src/app/comp.ts': `
|
|
213
|
+
import { TextField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
214
|
+
const f = new TextField({ type: FieldType.String, name: 'x' });
|
|
215
|
+
`,
|
|
216
|
+
});
|
|
217
|
+
const text = result.readText('src/app/comp.ts');
|
|
218
|
+
expect(text).toContain('as TextFieldConfig');
|
|
219
|
+
expect(text).toMatch(/import\s*\{[^}]*\bTextFieldConfig\b[^}]*\}\s*from\s*'@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
220
|
+
});
|
|
221
|
+
it('converte new XxxField(variavel) — argumento não é objeto literal inline', async () => {
|
|
222
|
+
const result = await runMigration({
|
|
223
|
+
'src/app/comp.ts': `
|
|
224
|
+
import { CalendarField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
225
|
+
function createCalendar(config: any) {
|
|
226
|
+
const calendar = new CalendarField(config);
|
|
227
|
+
calendar.appendTo = 'body';
|
|
228
|
+
return calendar;
|
|
229
|
+
}
|
|
230
|
+
`,
|
|
231
|
+
});
|
|
232
|
+
const text = result.readText('src/app/comp.ts');
|
|
233
|
+
expect(text).not.toContain('new CalendarField');
|
|
234
|
+
expect(text).not.toContain('CalendarField,');
|
|
235
|
+
expect(text).toContain('const calendar = config as CalendarFieldConfig;');
|
|
236
|
+
expect(text).toMatch(/import\s*\{[^}]*\bCalendarFieldConfig\b[^}]*\}\s*from\s*'@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
237
|
+
});
|
|
238
|
+
it('converte new XxxField(variavel) dentro de array — sem cast', async () => {
|
|
239
|
+
const result = await runMigration({
|
|
240
|
+
'src/app/comp.ts': `
|
|
241
|
+
import { CalendarField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
242
|
+
function build(config: any) {
|
|
243
|
+
return [new CalendarField(config)];
|
|
244
|
+
}
|
|
245
|
+
`,
|
|
246
|
+
});
|
|
247
|
+
const text = result.readText('src/app/comp.ts');
|
|
248
|
+
expect(text).not.toContain('new CalendarField');
|
|
249
|
+
expect(text).toContain('return [config];');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
// ─── A2b: Boolean + representedBy:'switch' → booleanSwitch ───────────────────
|
|
253
|
+
describe('migrate-dynamic-form-v19 — A2b: Boolean + representedBy → booleanSwitch', () => {
|
|
254
|
+
it('converte boolean+representedBy:switch → booleanSwitch e remove representedBy', async () => {
|
|
255
|
+
const result = await runMigration({
|
|
256
|
+
'src/app/comp.ts': `
|
|
257
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
258
|
+
const f = new FormField({ type: FieldType.Boolean, representedBy: 'switch', name: 'active' });
|
|
259
|
+
`,
|
|
260
|
+
});
|
|
261
|
+
const text = result.readText('src/app/comp.ts');
|
|
262
|
+
expect(text).toContain("'booleanSwitch'");
|
|
263
|
+
expect(text).not.toContain('representedBy');
|
|
264
|
+
expect(text).not.toContain("'boolean'");
|
|
265
|
+
});
|
|
266
|
+
it('não remove representedBy quando valor não é switch', async () => {
|
|
267
|
+
const result = await runMigration({
|
|
268
|
+
'src/app/comp.ts': `
|
|
269
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
270
|
+
const f = new FormField({ type: FieldType.Boolean, representedBy: 'checkbox', name: 'active' });
|
|
271
|
+
`,
|
|
272
|
+
});
|
|
273
|
+
const text = result.readText('src/app/comp.ts');
|
|
274
|
+
expect(text).toContain('representedBy');
|
|
275
|
+
expect(text).toContain("'boolean'");
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
// ─── A3: Type annotations ─────────────────────────────────────────────────────
|
|
279
|
+
describe('migrate-dynamic-form-v19 — A3: type annotations', () => {
|
|
280
|
+
it('converte FormField[] → DynamicType[]', async () => {
|
|
281
|
+
const result = await runMigration({
|
|
282
|
+
'src/app/comp.ts': `
|
|
283
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
284
|
+
export class C { fields: FormField[] = []; }
|
|
285
|
+
`,
|
|
286
|
+
});
|
|
287
|
+
const text = result.readText('src/app/comp.ts');
|
|
288
|
+
expect(text).toContain('DynamicType[]');
|
|
289
|
+
expect(text).not.toContain('FormField[]');
|
|
290
|
+
});
|
|
291
|
+
it('converte Field[] → DynamicType[]', async () => {
|
|
292
|
+
const result = await runMigration({
|
|
293
|
+
'src/app/comp.ts': `
|
|
294
|
+
import { Field } from '@seniorsistemas/angular-components/dynamic-form';
|
|
295
|
+
export class C { fields: Field[] = []; }
|
|
296
|
+
`,
|
|
297
|
+
});
|
|
298
|
+
const text = result.readText('src/app/comp.ts');
|
|
299
|
+
expect(text).toContain('DynamicType[]');
|
|
300
|
+
expect(text).not.toContain('Field[]');
|
|
301
|
+
});
|
|
302
|
+
it('converte DynamicConfig[] → DynamicStructure[]', async () => {
|
|
303
|
+
const result = await runMigration({
|
|
304
|
+
'src/app/comp.ts': `
|
|
305
|
+
import { DynamicConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
306
|
+
export class C { get configs(): DynamicConfig[] { return []; } }
|
|
307
|
+
`,
|
|
308
|
+
});
|
|
309
|
+
const text = result.readText('src/app/comp.ts');
|
|
310
|
+
expect(text).toContain('DynamicStructure[]');
|
|
311
|
+
expect(text).not.toContain('DynamicConfig[]');
|
|
312
|
+
});
|
|
313
|
+
it('converte FormField em parâmetro de método → DynamicType', async () => {
|
|
314
|
+
const result = await runMigration({
|
|
315
|
+
'src/app/comp.ts': `
|
|
316
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
317
|
+
export class C { build(fields: FormField[]): void {} }
|
|
318
|
+
`,
|
|
319
|
+
});
|
|
320
|
+
const text = result.readText('src/app/comp.ts');
|
|
321
|
+
expect(text).toContain('DynamicType[]');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
// ─── A4: Import cleanup ───────────────────────────────────────────────────────
|
|
325
|
+
describe('migrate-dynamic-form-v19 — A4: import cleanup', () => {
|
|
326
|
+
it('remove FormField, FieldType, TextField dos imports', async () => {
|
|
327
|
+
const result = await runMigration({
|
|
328
|
+
'src/app/comp.ts': `
|
|
329
|
+
import { FormField, FieldType, TextField, DynamicFormModule } from '@seniorsistemas/angular-components/dynamic-form';
|
|
330
|
+
const f = [new FormField({ type: FieldType.String, name: 'x' })];
|
|
331
|
+
`,
|
|
332
|
+
});
|
|
333
|
+
const text = result.readText('src/app/comp.ts');
|
|
334
|
+
expect(text).not.toContain("import { FormField");
|
|
335
|
+
expect(text).not.toContain('FormField');
|
|
336
|
+
expect(text).not.toContain('TextField');
|
|
337
|
+
expect(text).toContain('DynamicFormModule'); // DynamicFormModule must be kept
|
|
338
|
+
});
|
|
339
|
+
it('adiciona DynamicType ao import quando usado como type annotation', async () => {
|
|
340
|
+
const result = await runMigration({
|
|
341
|
+
'src/app/comp.ts': `
|
|
342
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
343
|
+
export class C { fields: FormField[] = []; }
|
|
344
|
+
`,
|
|
345
|
+
});
|
|
346
|
+
const text = result.readText('src/app/comp.ts');
|
|
347
|
+
expect(text).toContain('DynamicType');
|
|
348
|
+
expect(text).toContain(`from '@seniorsistemas/angular-components/dynamic-form'`);
|
|
349
|
+
});
|
|
350
|
+
it('adiciona DynamicStructure quando DynamicConfig é convertido', async () => {
|
|
351
|
+
const result = await runMigration({
|
|
352
|
+
'src/app/comp.ts': `
|
|
353
|
+
import { DynamicConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
354
|
+
export class C { get c(): DynamicConfig[] { return []; } }
|
|
355
|
+
`,
|
|
356
|
+
});
|
|
357
|
+
const text = result.readText('src/app/comp.ts');
|
|
358
|
+
expect(text).toContain('DynamicStructure');
|
|
359
|
+
});
|
|
360
|
+
it('mantém FieldType no import se usado como type annotation', async () => {
|
|
361
|
+
const result = await runMigration({
|
|
362
|
+
'src/app/comp.ts': `
|
|
363
|
+
import { FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
364
|
+
type MyMap = Record<string, FieldType>;
|
|
365
|
+
`,
|
|
366
|
+
});
|
|
367
|
+
const text = result.readText('src/app/comp.ts');
|
|
368
|
+
expect(text).toContain('FieldType');
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
// ─── A5: Editor field ─────────────────────────────────────────────────────────
|
|
372
|
+
describe('migrate-dynamic-form-v19 — A5: Editor field', () => {
|
|
373
|
+
it('converte FieldType.Editor → editor e mantém props como modules', async () => {
|
|
374
|
+
const result = await runMigration({
|
|
375
|
+
'src/app/comp.ts': `
|
|
376
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
377
|
+
const f = [new FormField({ type: FieldType.Editor, name: 'desc', modules: { toolbar: [] } })];
|
|
378
|
+
`,
|
|
379
|
+
'src/app/app.config.ts': `
|
|
380
|
+
import { ApplicationConfig } from '@angular/core';
|
|
381
|
+
export const appConfig: ApplicationConfig = { providers: [] };
|
|
382
|
+
`,
|
|
383
|
+
});
|
|
384
|
+
const text = result.readText('src/app/comp.ts');
|
|
385
|
+
expect(text).toContain("'editor'");
|
|
386
|
+
expect(text).toContain('modules');
|
|
387
|
+
expect(text).not.toContain('FieldType.Editor');
|
|
388
|
+
});
|
|
389
|
+
it('adiciona provideEditorField() ao app.config.ts quando Editor detectado', async () => {
|
|
390
|
+
const result = await runMigration({
|
|
391
|
+
'src/app/comp.ts': `
|
|
392
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
393
|
+
const f = [new FormField({ type: FieldType.Editor, name: 'desc' })];
|
|
394
|
+
`,
|
|
395
|
+
'src/app/app.config.ts': `
|
|
396
|
+
import { ApplicationConfig } from '@angular/core';
|
|
397
|
+
export const appConfig: ApplicationConfig = { providers: [] };
|
|
398
|
+
`,
|
|
399
|
+
});
|
|
400
|
+
const configText = result.readText('src/app/app.config.ts');
|
|
401
|
+
expect(configText).toContain('provideEditorField()');
|
|
402
|
+
expect(configText).toContain("from '@seniorsistemas/angular-components/optional-fields/editor'");
|
|
403
|
+
});
|
|
404
|
+
it('remove quill do package.json quando Editor não é usado', async () => {
|
|
405
|
+
const result = await runMigration({
|
|
406
|
+
'src/app/comp.ts': `
|
|
407
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
408
|
+
const f = [new FormField({ type: FieldType.String, name: 'x' })];
|
|
409
|
+
`,
|
|
410
|
+
'/package.json': DEFAULT_PACKAGE_JSON,
|
|
411
|
+
});
|
|
412
|
+
const pkg = JSON.parse(result.readText('/package.json'));
|
|
413
|
+
expect(pkg.dependencies?.quill).toBeUndefined();
|
|
414
|
+
});
|
|
415
|
+
it('mantém quill no package.json quando Editor é usado', async () => {
|
|
416
|
+
const result = await runMigration({
|
|
417
|
+
'src/app/comp.ts': `
|
|
418
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
419
|
+
const f = [new FormField({ type: FieldType.Editor, name: 'desc' })];
|
|
420
|
+
`,
|
|
421
|
+
'src/app/app.config.ts': `
|
|
422
|
+
import { ApplicationConfig } from '@angular/core';
|
|
423
|
+
export const appConfig: ApplicationConfig = { providers: [] };
|
|
424
|
+
`,
|
|
425
|
+
'/package.json': DEFAULT_PACKAGE_JSON,
|
|
426
|
+
});
|
|
427
|
+
const pkg = JSON.parse(result.readText('/package.json'));
|
|
428
|
+
expect(pkg.dependencies?.quill).toBeDefined();
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
// ─── A5b: angular.json styles ─────────────────────────────────────────────────
|
|
432
|
+
describe('migrate-dynamic-form-v19 — A5b: angular.json styles', () => {
|
|
433
|
+
it('adiciona styles.css ao angular.json', async () => {
|
|
434
|
+
const result = await runMigration({
|
|
435
|
+
'src/app/comp.ts': `
|
|
436
|
+
import { FormField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
437
|
+
const f = [new FormField({ type: FieldType.String, name: 'x' })];
|
|
438
|
+
`,
|
|
439
|
+
});
|
|
440
|
+
const json = JSON.parse(result.readText('/angular.json'));
|
|
441
|
+
const styles = json.projects.app.architect.build.options.styles;
|
|
442
|
+
expect(styles).toContain('node_modules/@seniorsistemas/angular-components/styles.css');
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
// ─── B: HTML [fields] → [configs] ─────────────────────────────────────────────
|
|
446
|
+
describe('migrate-dynamic-form-v19 — B: HTML [fields] → [configs]', () => {
|
|
447
|
+
it('substitui [fields]="fields" por [configs]="fieldsConfigs" no HTML', async () => {
|
|
448
|
+
const result = await runMigration({
|
|
449
|
+
'src/app/comp.html': `<s-dynamic-form [form]="form" [fields]="fields"></s-dynamic-form>`,
|
|
450
|
+
'src/app/comp.ts': `
|
|
451
|
+
import { DynamicFormModule } from '@seniorsistemas/angular-components/dynamic-form';
|
|
452
|
+
export class C { fields = []; }
|
|
453
|
+
`,
|
|
454
|
+
});
|
|
455
|
+
const html = result.readText('src/app/comp.html');
|
|
456
|
+
expect(html).toContain('[configs]="fieldsConfigs"');
|
|
457
|
+
expect(html).not.toContain('[fields]');
|
|
458
|
+
});
|
|
459
|
+
it('gera getter protected get fieldsConfigs() no .ts correspondente', async () => {
|
|
460
|
+
const result = await runMigration({
|
|
461
|
+
'src/app/comp.html': `<s-dynamic-form [form]="form" [fields]="fields"></s-dynamic-form>`,
|
|
462
|
+
'src/app/comp.ts': `
|
|
463
|
+
import { DynamicFormModule } from '@seniorsistemas/angular-components/dynamic-form';
|
|
464
|
+
export class C { fields = []; }
|
|
465
|
+
`,
|
|
466
|
+
});
|
|
467
|
+
const ts = result.readText('src/app/comp.ts');
|
|
468
|
+
expect(ts).toContain('get fieldsConfigs()');
|
|
469
|
+
expect(ts).toContain('dynamicTypeToDynamicStructure');
|
|
470
|
+
expect(ts).toContain('DynamicStructure');
|
|
471
|
+
});
|
|
472
|
+
it('trata [fields]="tab.fields" (membro de objeto) com helper method', async () => {
|
|
473
|
+
const result = await runMigration({
|
|
474
|
+
'src/app/comp.html': `<s-dynamic-form [form]="form" [fields]="tab.fields"></s-dynamic-form>`,
|
|
475
|
+
'src/app/comp.ts': `
|
|
476
|
+
import { DynamicFormModule } from '@seniorsistemas/angular-components/dynamic-form';
|
|
477
|
+
export class C { tab: any = {}; }
|
|
478
|
+
`,
|
|
479
|
+
});
|
|
480
|
+
const html = result.readText('src/app/comp.html');
|
|
481
|
+
const ts = result.readText('src/app/comp.ts');
|
|
482
|
+
expect(html).toContain('[configs]="dynamicTypeToConfigs(tab.fields)"');
|
|
483
|
+
expect(ts).toContain('dynamicTypeToConfigs(fields: DynamicType[]): DynamicStructure[]');
|
|
484
|
+
});
|
|
485
|
+
it('não altera HTML sem [fields] no s-dynamic-form', async () => {
|
|
486
|
+
const original = `<s-dynamic-form [form]="form" [configs]="configs"></s-dynamic-form>`;
|
|
487
|
+
const result = await runMigration({
|
|
488
|
+
'src/app/comp.html': original,
|
|
489
|
+
'src/app/comp.ts': `export class C {}`,
|
|
490
|
+
});
|
|
491
|
+
expect(result.readText('src/app/comp.html')).toBe(original);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
// ─── Cenário completo ─────────────────────────────────────────────────────────
|
|
495
|
+
describe('migrate-dynamic-form-v19 — cenário completo', () => {
|
|
496
|
+
it('migra arquivo completo com múltiplos tipos de campo', async () => {
|
|
497
|
+
const result = await runMigration({
|
|
498
|
+
'src/app/comp.ts': `
|
|
499
|
+
import { Field, FieldType, FormField, CalendarField, SelectField, DynamicConfig, DynamicFormModule }
|
|
500
|
+
from '@seniorsistemas/angular-components/dynamic-form';
|
|
501
|
+
|
|
502
|
+
export class MyComponent {
|
|
503
|
+
fields: Field[] = [
|
|
504
|
+
new FormField({ type: FieldType.String, name: 'nome', label: 'Nome' }),
|
|
505
|
+
new CalendarField({ type: FieldType.Date, name: 'dt', label: 'Data' }),
|
|
506
|
+
new SelectField({ type: FieldType.Enum, name: 'status', label: 'Status', options: [] }),
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
get configs(): DynamicConfig[] {
|
|
510
|
+
return [{ type: 'row', fields: this.fields }];
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
`,
|
|
514
|
+
});
|
|
515
|
+
const text = result.readText('src/app/comp.ts');
|
|
516
|
+
expect(text).toContain("{ type: 'string', name: 'nome', label: 'Nome' }");
|
|
517
|
+
expect(text).toContain("{ type: 'date', name: 'dt', label: 'Data' }");
|
|
518
|
+
expect(text).toContain("{ type: 'enum', name: 'status', label: 'Status', options: [] }");
|
|
519
|
+
expect(text).toContain('DynamicType[]');
|
|
520
|
+
expect(text).toContain('DynamicStructure[]');
|
|
521
|
+
expect(text).toContain('DynamicFormModule');
|
|
522
|
+
expect(text).not.toContain('FormField');
|
|
523
|
+
expect(text).not.toContain('CalendarField');
|
|
524
|
+
expect(text).not.toContain('SelectField');
|
|
525
|
+
expect(text).not.toContain('FieldType');
|
|
526
|
+
expect(text).not.toContain('DynamicConfig');
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
// ─── Regressão: mapa de factories por tipo (caso real reportado) ─────────────
|
|
530
|
+
describe('migrate-dynamic-form-v19 — regressão: mapa de creators por FilterType', () => {
|
|
531
|
+
it('migra um Record<string, () => FormField> com new XxxField em corpo conciso e por variável, sem quebrar o parsing', async () => {
|
|
532
|
+
const result = await runMigration({
|
|
533
|
+
'src/app/comp.ts': `
|
|
534
|
+
import { TextField, CalendarField, FieldType } from '@seniorsistemas/angular-components/dynamic-form';
|
|
535
|
+
|
|
536
|
+
export class FormFilterComponent {
|
|
537
|
+
private createField(filterType: string, field: any) {
|
|
538
|
+
const creators = {
|
|
539
|
+
string: () => new TextField({ ...field, type: FieldType.String }),
|
|
540
|
+
date: () => this.createCalendar({ ...field, type: FieldType.Date }),
|
|
541
|
+
};
|
|
542
|
+
return creators[filterType]();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private createCalendar(config: any) {
|
|
546
|
+
const calendar = new CalendarField(config);
|
|
547
|
+
calendar.appendTo = 'body';
|
|
548
|
+
return calendar;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
`,
|
|
552
|
+
});
|
|
553
|
+
const text = result.readText('src/app/comp.ts');
|
|
554
|
+
expect(text).toContain("string: () => ({ ...field, type: 'string' } as TextFieldConfig),");
|
|
555
|
+
expect(text).toContain('date: () => this.createCalendar({ ...field, type: \'date\' }),');
|
|
556
|
+
expect(text).toContain('const calendar = config as CalendarFieldConfig;');
|
|
557
|
+
expect(text).not.toContain('new TextField');
|
|
558
|
+
expect(text).not.toContain('new CalendarField');
|
|
559
|
+
// both interfaces used in isolated casts must be imported
|
|
560
|
+
expect(text).toMatch(/\bTextFieldConfig\b.*from '@seniorsistemas\/angular-components\/dynamic-form'/s);
|
|
561
|
+
expect(text).toMatch(/\bCalendarFieldConfig\b.*from '@seniorsistemas\/angular-components\/dynamic-form'/s);
|
|
562
|
+
// the class body must still parse correctly — createCalendar must remain a sibling method,
|
|
563
|
+
// not get swallowed by a stray unbalanced block from a botched arrow-function replacement
|
|
564
|
+
expect(text).toContain('private createCalendar(config: any) {');
|
|
565
|
+
expect((text.match(/private /g) ?? []).length).toBe(2);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
// ─── Field array annotation ────────────────────────────────────────────────────
|
|
569
|
+
describe('migrate-dynamic-form-v19 — field array annotation', () => {
|
|
570
|
+
it('adiciona ": DynamicType[]" em const sem tipo com array de fields (strict)', async () => {
|
|
571
|
+
const result = await runMigration({
|
|
572
|
+
'src/app/lookup.ts': `
|
|
573
|
+
const defaultFields = [
|
|
574
|
+
{ name: "number", type: 'integer' },
|
|
575
|
+
{ name: "description", type: 'string' }
|
|
576
|
+
];
|
|
577
|
+
`,
|
|
578
|
+
});
|
|
579
|
+
const text = result.readText('src/app/lookup.ts');
|
|
580
|
+
expect(text).toContain('const defaultFields: DynamicType[] = [');
|
|
581
|
+
expect(text).toMatch(/import.*DynamicType.*from '@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
582
|
+
});
|
|
583
|
+
it('NÃO modifica const já com tipo explícito', async () => {
|
|
584
|
+
const original = `
|
|
585
|
+
const defaultFields: SomeType[] = [
|
|
586
|
+
{ name: "number", type: 'integer' }
|
|
587
|
+
];
|
|
588
|
+
`;
|
|
589
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
590
|
+
expect(result.readText('src/app/lookup.ts')).toBe(original);
|
|
591
|
+
});
|
|
592
|
+
it('NÃO modifica array com elementos sem type válido de field', async () => {
|
|
593
|
+
const original = `
|
|
594
|
+
const routes = [
|
|
595
|
+
{ name: 'home', path: '/home' },
|
|
596
|
+
{ name: 'about', path: '/about' }
|
|
597
|
+
];
|
|
598
|
+
`;
|
|
599
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
600
|
+
expect(result.readText('src/app/lookup.ts')).toBe(original);
|
|
601
|
+
});
|
|
602
|
+
it('adiciona "as DynamicType[]" em arrow function sem return type com array body (strict)', async () => {
|
|
603
|
+
const result = await runMigration({
|
|
604
|
+
'src/app/lookup.ts': `
|
|
605
|
+
const getFields = ({ translateService }: { translateService: any }) => [
|
|
606
|
+
{ name: "aliquota", type: 'number' },
|
|
607
|
+
{ name: "vigencia", type: 'date' }
|
|
608
|
+
];
|
|
609
|
+
`,
|
|
610
|
+
});
|
|
611
|
+
const text = result.readText('src/app/lookup.ts');
|
|
612
|
+
expect(text).toContain('] as DynamicType[]');
|
|
613
|
+
expect(text).toMatch(/import.*DynamicType.*from '@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
614
|
+
});
|
|
615
|
+
it('NÃO modifica arrow function com return type explícito', async () => {
|
|
616
|
+
const original = `
|
|
617
|
+
const getFields = (): SomeType[] => [
|
|
618
|
+
{ name: "x", type: 'string' }
|
|
619
|
+
];
|
|
620
|
+
`;
|
|
621
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
622
|
+
expect(result.readText('src/app/lookup.ts')).toBe(original);
|
|
623
|
+
});
|
|
624
|
+
it('adiciona ": DynamicType[]" em array com spread objects + name/type (lenient)', async () => {
|
|
625
|
+
const result = await runMigration({
|
|
626
|
+
'src/app/lookup.ts': `
|
|
627
|
+
const lookupService: any = {};
|
|
628
|
+
const searchFields = [
|
|
629
|
+
{ ...lookupService.mountOptions({}), name: "origem" },
|
|
630
|
+
{ name: "aliquota", type: 'number' }
|
|
631
|
+
];
|
|
632
|
+
`,
|
|
633
|
+
});
|
|
634
|
+
const text = result.readText('src/app/lookup.ts');
|
|
635
|
+
expect(text).toContain('const searchFields: DynamicType[] = [');
|
|
636
|
+
expect(text).toMatch(/import.*DynamicType.*from '@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
637
|
+
});
|
|
638
|
+
it('NÃO modifica array com spread sem nenhum elemento field-like', async () => {
|
|
639
|
+
const original = `
|
|
640
|
+
const cfg = [
|
|
641
|
+
{ ...base, label: 'Home' },
|
|
642
|
+
...extra
|
|
643
|
+
];
|
|
644
|
+
`;
|
|
645
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
646
|
+
expect(result.readText('src/app/lookup.ts')).toBe(original);
|
|
647
|
+
});
|
|
648
|
+
it('adiciona "as DynamicType[]" em array com SpreadElement inline como argumento de função (lenient)', async () => {
|
|
649
|
+
const result = await runMigration({
|
|
650
|
+
'src/app/lookup.ts': `
|
|
651
|
+
function buildLookup(fields: any[]) {}
|
|
652
|
+
const getExtra = () => [{ name: 'x', type: 'string' }];
|
|
653
|
+
buildLookup([
|
|
654
|
+
{ name: "number", type: 'integer' },
|
|
655
|
+
...getExtra()
|
|
656
|
+
]);
|
|
657
|
+
`,
|
|
658
|
+
});
|
|
659
|
+
const text = result.readText('src/app/lookup.ts');
|
|
660
|
+
expect(text).toContain('] as DynamicType[]');
|
|
661
|
+
expect(text).toMatch(/import.*DynamicType.*from '@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
662
|
+
});
|
|
663
|
+
it('adiciona import DynamicType quando ausente', async () => {
|
|
664
|
+
const result = await runMigration({
|
|
665
|
+
'src/app/lookup.ts': `
|
|
666
|
+
const f = [{ name: 'x', type: 'boolean' }];
|
|
667
|
+
`,
|
|
668
|
+
});
|
|
669
|
+
const text = result.readText('src/app/lookup.ts');
|
|
670
|
+
expect(text).toMatch(/import.*DynamicType.*from '@seniorsistemas\/angular-components\/dynamic-form'/);
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
// ─── Enum/select sem options ───────────────────────────────────────────────────
|
|
674
|
+
describe('migrate-dynamic-form-v19 — enum/select sem options', () => {
|
|
675
|
+
it('NÃO modifica o código para { type: \'enum\' } sem options (apenas warn)', async () => {
|
|
676
|
+
const original = `const f = [{ name: "imposto", type: 'enum' }];`;
|
|
677
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
678
|
+
// file must remain syntactically intact — type widening annotation may be added
|
|
679
|
+
expect(result.readText('src/app/lookup.ts')).toContain("type: 'enum'");
|
|
680
|
+
});
|
|
681
|
+
it('NÃO modifica { type: \'enum\', options: [...] } (já tem options)', async () => {
|
|
682
|
+
const original = `const f = [{ name: "x", type: 'enum', options: [{ value: 1, label: 'A' }] }];`;
|
|
683
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
684
|
+
expect(result.readText('src/app/lookup.ts')).toContain("options: [");
|
|
685
|
+
});
|
|
686
|
+
it('NÃO modifica { type: \'enum\', options: fn() } com função de options', async () => {
|
|
687
|
+
const original = `
|
|
688
|
+
function getOpts() { return []; }
|
|
689
|
+
const f = [{ name: "x", type: 'enum', options: getOpts() }];
|
|
690
|
+
`;
|
|
691
|
+
const result = await runMigration({ 'src/app/lookup.ts': original });
|
|
692
|
+
expect(result.readText('src/app/lookup.ts')).toContain('options: getOpts()');
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
// ─── FormField heritage ────────────────────────────────────────────────────────
|
|
696
|
+
describe('migrate-dynamic-form-v19 — FormField heritage warning/fix', () => {
|
|
697
|
+
it('remove "extends FormField" de interface quando é o único extends', async () => {
|
|
698
|
+
const result = await runMigration({
|
|
699
|
+
'src/app/erp-lookups.ts': `
|
|
700
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
701
|
+
export interface FormFieldErpx extends FormField { maxLength?: number; }
|
|
702
|
+
`,
|
|
703
|
+
});
|
|
704
|
+
const text = result.readText('src/app/erp-lookups.ts');
|
|
705
|
+
expect(text).toContain('export interface FormFieldErpx');
|
|
706
|
+
expect(text).not.toContain('extends FormField');
|
|
707
|
+
expect(text).not.toMatch(/import.*FormField/);
|
|
708
|
+
});
|
|
709
|
+
it('remove "FormField" de interface com múltiplos extends (mantém os outros)', async () => {
|
|
710
|
+
const result = await runMigration({
|
|
711
|
+
'src/app/erp-lookups.ts': `
|
|
712
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
713
|
+
export interface MyField extends FormField, OtherBase { extra?: string; }
|
|
714
|
+
`,
|
|
715
|
+
});
|
|
716
|
+
const text = result.readText('src/app/erp-lookups.ts');
|
|
717
|
+
expect(text).toContain('extends OtherBase');
|
|
718
|
+
expect(text).not.toContain('FormField');
|
|
719
|
+
});
|
|
720
|
+
it('mantém "extends FormField" em classe e não remove (apenas warn)', async () => {
|
|
721
|
+
const result = await runMigration({
|
|
722
|
+
'src/app/erp-lookups.ts': `
|
|
723
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
724
|
+
export class ErpLookupsFormField extends FormField {
|
|
725
|
+
constructor(config: any) { super(config); }
|
|
726
|
+
}
|
|
727
|
+
`,
|
|
728
|
+
});
|
|
729
|
+
const text = result.readText('src/app/erp-lookups.ts');
|
|
730
|
+
expect(text).toContain('class ErpLookupsFormField');
|
|
731
|
+
expect(text).toContain('extends FormField');
|
|
732
|
+
});
|
|
733
|
+
it('arquivo com ConstructorParameters<typeof FormField> permanece sintaticamente íntegro', async () => {
|
|
734
|
+
const result = await runMigration({
|
|
735
|
+
'src/app/erp-lookups.ts': `
|
|
736
|
+
import { FormField } from '@seniorsistemas/angular-components/dynamic-form';
|
|
737
|
+
export class Foo extends FormField {
|
|
738
|
+
constructor(...args: ConstructorParameters<typeof FormField>) { super(...args); }
|
|
739
|
+
}
|
|
740
|
+
`,
|
|
741
|
+
});
|
|
742
|
+
const text = result.readText('src/app/erp-lookups.ts');
|
|
743
|
+
expect(text).toContain('ConstructorParameters');
|
|
744
|
+
expect(text).toContain('class Foo');
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
// ─── BaseFieldComponentConfig: @Input → input signal ─────────────────────────
|
|
748
|
+
describe('migrate-dynamic-form-v19 — BaseFieldComponentConfig: @Input → input signal', () => {
|
|
749
|
+
it('converte @Input() field: Signal<CustomType> → field = input.required<CustomType>()', async () => {
|
|
750
|
+
const result = await runMigration({
|
|
751
|
+
'src/app/postalcode-field.component.ts': `
|
|
752
|
+
import { Component, Input, Signal } from '@angular/core';
|
|
753
|
+
import { FormControl } from '@angular/forms';
|
|
754
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
755
|
+
import { PostalCodeLookupField } from './postalcode-lookup-field';
|
|
756
|
+
|
|
757
|
+
@Component({ template: '' })
|
|
758
|
+
export class PostalCodeLookupFieldComponent implements BaseFieldComponentConfig {
|
|
759
|
+
@Input()
|
|
760
|
+
public field: Signal<PostalCodeLookupField>;
|
|
761
|
+
@Input()
|
|
762
|
+
public formControl: Signal<FormControl>;
|
|
763
|
+
}
|
|
764
|
+
`,
|
|
765
|
+
});
|
|
766
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
767
|
+
expect(text).toContain('field = input.required<PostalCodeLookupField>()');
|
|
768
|
+
expect(text).toContain('formControl = input<FormControl>()');
|
|
769
|
+
expect(text).not.toContain('@Input()');
|
|
770
|
+
});
|
|
771
|
+
it('converte @Input() field: CustomType (sem Signal wrapper) → field = input.required<CustomType>()', async () => {
|
|
772
|
+
const result = await runMigration({
|
|
773
|
+
'src/app/postalcode-field.component.ts': `
|
|
774
|
+
import { Component, Input } from '@angular/core';
|
|
775
|
+
import { FormControl } from '@angular/forms';
|
|
776
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
777
|
+
import { PostalCodeField } from './postalcode-field';
|
|
778
|
+
|
|
779
|
+
@Component({ template: '' })
|
|
780
|
+
export class PostalCodeFieldComponent implements BaseFieldComponentConfig {
|
|
781
|
+
@Input()
|
|
782
|
+
field: PostalCodeField;
|
|
783
|
+
@Input()
|
|
784
|
+
formControl: FormControl;
|
|
785
|
+
}
|
|
786
|
+
`,
|
|
787
|
+
});
|
|
788
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
789
|
+
expect(text).toContain('field = input.required<PostalCodeField>()');
|
|
790
|
+
expect(text).toContain('formControl = input<FormControl>()');
|
|
791
|
+
});
|
|
792
|
+
it('NÃO converte @Input() em classe que NÃO implementa BaseFieldComponentConfig', async () => {
|
|
793
|
+
const original = `
|
|
794
|
+
import { Component, Input } from '@angular/core';
|
|
795
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
796
|
+
|
|
797
|
+
@Component({ template: '' })
|
|
798
|
+
export class SomeOtherComponent {
|
|
799
|
+
@Input()
|
|
800
|
+
field: string;
|
|
801
|
+
}
|
|
802
|
+
`;
|
|
803
|
+
const result = await runMigration({ 'src/app/comp.ts': original });
|
|
804
|
+
expect(result.readText('src/app/comp.ts')).toContain('@Input()');
|
|
805
|
+
expect(result.readText('src/app/comp.ts')).not.toContain('input.required');
|
|
806
|
+
});
|
|
807
|
+
it('preserva outros @Input() que não são field/formControl', async () => {
|
|
808
|
+
const result = await runMigration({
|
|
809
|
+
'src/app/postalcode-field.component.ts': `
|
|
810
|
+
import { Component, Input, Signal } from '@angular/core';
|
|
811
|
+
import { FormControl } from '@angular/forms';
|
|
812
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
813
|
+
|
|
814
|
+
@Component({ template: '' })
|
|
815
|
+
export class MyFieldComponent implements BaseFieldComponentConfig {
|
|
816
|
+
@Input()
|
|
817
|
+
field: Signal<any>;
|
|
818
|
+
@Input()
|
|
819
|
+
formControl: Signal<FormControl>;
|
|
820
|
+
@Input()
|
|
821
|
+
label: string;
|
|
822
|
+
}
|
|
823
|
+
`,
|
|
824
|
+
});
|
|
825
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
826
|
+
expect(text).toContain('field = input.required<any>()');
|
|
827
|
+
expect(text).toContain('formControl = input<FormControl>()');
|
|
828
|
+
expect(text).toContain('@Input()');
|
|
829
|
+
expect(text).toContain('label: string');
|
|
830
|
+
});
|
|
831
|
+
it('adiciona import { input } a @angular/core', async () => {
|
|
832
|
+
const result = await runMigration({
|
|
833
|
+
'src/app/postalcode-field.component.ts': `
|
|
834
|
+
import { Component, Input } from '@angular/core';
|
|
835
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
836
|
+
|
|
837
|
+
@Component({ template: '' })
|
|
838
|
+
export class MyFieldComponent implements BaseFieldComponentConfig {
|
|
839
|
+
@Input()
|
|
840
|
+
field: any;
|
|
841
|
+
@Input()
|
|
842
|
+
formControl: any;
|
|
843
|
+
}
|
|
844
|
+
`,
|
|
845
|
+
});
|
|
846
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
847
|
+
expect(text).toMatch(/import\s*\{[^}]*\binput\b[^}]*\}\s*from\s*'@angular\/core'/);
|
|
848
|
+
});
|
|
849
|
+
it('remove Input de @angular/core quando não há mais @Input() no arquivo', async () => {
|
|
850
|
+
const result = await runMigration({
|
|
851
|
+
'src/app/postalcode-field.component.ts': `
|
|
852
|
+
import { Component, Input } from '@angular/core';
|
|
853
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
854
|
+
|
|
855
|
+
@Component({ template: '' })
|
|
856
|
+
export class MyFieldComponent implements BaseFieldComponentConfig {
|
|
857
|
+
@Input()
|
|
858
|
+
field: any;
|
|
859
|
+
@Input()
|
|
860
|
+
formControl: any;
|
|
861
|
+
}
|
|
862
|
+
`,
|
|
863
|
+
});
|
|
864
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
865
|
+
expect(text).not.toMatch(/import\s*\{[^}]*\bInput\b[^}]*\}\s*from\s*'@angular\/core'/);
|
|
866
|
+
});
|
|
867
|
+
it('mantém Input em @angular/core quando há outros @Input() no arquivo', async () => {
|
|
868
|
+
const result = await runMigration({
|
|
869
|
+
'src/app/postalcode-field.component.ts': `
|
|
870
|
+
import { Component, Input } from '@angular/core';
|
|
871
|
+
import { BaseFieldComponentConfig } from '@seniorsistemas/angular-components/dynamic-form';
|
|
872
|
+
|
|
873
|
+
@Component({ template: '' })
|
|
874
|
+
export class MyFieldComponent implements BaseFieldComponentConfig {
|
|
875
|
+
@Input()
|
|
876
|
+
field: any;
|
|
877
|
+
@Input()
|
|
878
|
+
formControl: any;
|
|
879
|
+
@Input()
|
|
880
|
+
label: string;
|
|
881
|
+
}
|
|
882
|
+
`,
|
|
883
|
+
});
|
|
884
|
+
const text = result.readText('src/app/postalcode-field.component.ts');
|
|
885
|
+
expect(text).toMatch(/import\s*\{[^}]*\bInput\b[^}]*\}\s*from\s*'@angular\/core'/);
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
//# sourceMappingURL=index.test.js.map
|