@dynamic-field-kit/angular 1.3.3 → 1.4.0
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/CHANGELOG.md +19 -0
- package/README.md +50 -3
- package/dist/README.md +50 -3
- package/dist/components/BaseInput.d.ts +5 -11
- package/dist/components/DynamicInput.d.ts +6 -2
- package/dist/components/FieldInput.d.ts +6 -2
- package/dist/components/MultiFieldInput.d.ts +10 -1
- package/dist/esm2022/components/BaseInput.mjs +8 -17
- package/dist/esm2022/components/DynamicInput.mjs +67 -14
- package/dist/esm2022/components/FieldInput.mjs +39 -20
- package/dist/esm2022/components/MultiFieldInput.mjs +87 -13
- package/dist/esm2022/fieldRegistryToken.mjs +12 -0
- package/dist/esm2022/public-api.mjs +6 -2
- package/dist/esm2022/types/layout.mjs +1 -1
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +208 -61
- package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +1 -1
- package/dist/fieldRegistryToken.d.ts +3 -0
- package/dist/public-api.d.ts +4 -1
- package/dist/types/layout.d.ts +4 -25
- package/package.json +14 -16
- package/src/components/BaseInput.ts +7 -10
- package/src/components/DynamicInput.ts +67 -11
- package/src/components/FieldInput.ts +23 -16
- package/src/components/MultiFieldInput.ts +101 -8
- package/src/fieldRegistryToken.ts +15 -0
- package/src/public-api.ts +40 -24
- package/src/types/layout.ts +14 -36
- package/test/DynamicInput.spec.ts +217 -17
- package/test/FieldInput.spec.ts +131 -29
- package/test/MultiFieldInput.spec.ts +256 -0
- package/test/helpers/renderers.ts +89 -0
- package/test/layout.spec.ts +119 -0
- package/test/publicApi.spec.ts +64 -0
- package/{test.ts → test/setup.ts} +2 -2
- package/test/smoke.spec.ts +27 -0
- package/tsconfig.json +13 -13
- package/tsconfig.spec.json +4 -16
- package/vitest.config.ts +30 -0
- package/angular.json +0 -27
- package/karma.conf.js +0 -37
- package/test/angular.spec.ts +0 -102
- package/test/integration.spec.ts +0 -57
- package/test/layouts.spec.ts +0 -26
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { FieldRegistry, fieldRegistry } from '@dynamic-field-kit/core';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import * as publicApi from '../src/public-api';
|
|
6
|
+
import { FIELD_REGISTRY } from '../src/fieldRegistryToken';
|
|
7
|
+
import { DynamicFieldKitModule } from '../src/lib/dynamic-field-kit.module';
|
|
8
|
+
import { makeRegistry, TextRendererComponent } from './helpers/renderers';
|
|
9
|
+
|
|
10
|
+
describe('public API', () => {
|
|
11
|
+
it('exports the components, registry and validation helpers', () => {
|
|
12
|
+
expect(publicApi.DynamicInput).toBeDefined();
|
|
13
|
+
expect(publicApi.FieldInput).toBeDefined();
|
|
14
|
+
expect(publicApi.MultiFieldInput).toBeDefined();
|
|
15
|
+
expect(publicApi.FIELD_REGISTRY).toBe(FIELD_REGISTRY);
|
|
16
|
+
expect(publicApi.FieldRegistry).toBe(FieldRegistry);
|
|
17
|
+
expect(typeof publicApi.validateField).toBe('function');
|
|
18
|
+
expect(typeof publicApi.validateFields).toBe('function');
|
|
19
|
+
expect(typeof publicApi.resolveDisabled).toBe('function');
|
|
20
|
+
expect(typeof publicApi.resolveReadOnly).toBe('function');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('FIELD_REGISTRY token', () => {
|
|
25
|
+
it('defaults to the process-wide singleton', () => {
|
|
26
|
+
TestBed.configureTestingModule({});
|
|
27
|
+
|
|
28
|
+
expect(TestBed.inject(FIELD_REGISTRY)).toBe(fieldRegistry);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('can be overridden with a scoped registry', () => {
|
|
32
|
+
const scoped = makeRegistry();
|
|
33
|
+
TestBed.configureTestingModule({
|
|
34
|
+
providers: [{ provide: FIELD_REGISTRY, useValue: scoped }],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(TestBed.inject(FIELD_REGISTRY)).toBe(scoped);
|
|
38
|
+
expect(TestBed.inject(FIELD_REGISTRY)).not.toBe(fieldRegistry);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('DynamicFieldKitModule', () => {
|
|
43
|
+
@Component({
|
|
44
|
+
standalone: true,
|
|
45
|
+
imports: [DynamicFieldKitModule],
|
|
46
|
+
template: `<dfk-field-input
|
|
47
|
+
[fieldDescription]="{ name: 'a', type: 'text' }"
|
|
48
|
+
></dfk-field-input>`,
|
|
49
|
+
})
|
|
50
|
+
class ModuleHost {}
|
|
51
|
+
|
|
52
|
+
it('exports the components for template use', () => {
|
|
53
|
+
const scoped = makeRegistry();
|
|
54
|
+
scoped.register('text', TextRendererComponent as never);
|
|
55
|
+
TestBed.configureTestingModule({
|
|
56
|
+
providers: [{ provide: FIELD_REGISTRY, useValue: scoped }],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const fixture = TestBed.createComponent(ModuleHost);
|
|
60
|
+
fixture.detectChanges();
|
|
61
|
+
|
|
62
|
+
expect(fixture.nativeElement.querySelector('input.txt')).not.toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { fieldRegistry } from '@dynamic-field-kit/core';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'dfk-smoke',
|
|
8
|
+
standalone: true,
|
|
9
|
+
template: `<span class="smoke">{{ label }}</span>`,
|
|
10
|
+
})
|
|
11
|
+
class SmokeComponent {
|
|
12
|
+
label = 'mounted';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('vitest + Angular infrastructure', () => {
|
|
16
|
+
it('imports @dynamic-field-kit/core', () => {
|
|
17
|
+
expect(typeof fieldRegistry.register).toBe('function');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('mounts a component through TestBed', () => {
|
|
21
|
+
const fixture = TestBed.createComponent(SmokeComponent);
|
|
22
|
+
fixture.detectChanges();
|
|
23
|
+
|
|
24
|
+
const el: HTMLElement = fixture.nativeElement.querySelector('.smoke');
|
|
25
|
+
expect(el.textContent).toBe('mounted');
|
|
26
|
+
});
|
|
27
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"composite": false,
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"target": "ES2019",
|
|
8
|
-
"module": "ES2020",
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"emitDecoratorMetadata": true
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*"]
|
|
13
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"composite": false,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"target": "ES2019",
|
|
8
|
+
"module": "ES2020",
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"emitDecoratorMetadata": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|
package/tsconfig.spec.json
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "./tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"outDir": "./
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"target": "ES2020",
|
|
8
|
-
"emitDecoratorMetadata": true,
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"allowSyntheticDefaultImports": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"declaration": false,
|
|
13
|
-
"sourceMap": true,
|
|
14
|
-
"baseUrl": ".",
|
|
15
|
-
"skipLibCheck": true,
|
|
16
|
-
"noImplicitAny": false,
|
|
17
|
-
"strict": false
|
|
4
|
+
"outDir": "./out-tsc/spec",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"useDefineForClassFields": true
|
|
18
7
|
},
|
|
19
|
-
"include": ["test/**/*.ts"]
|
|
20
|
-
"exclude": ["node_modules", "dist", "dist-spec", "examples", "src"]
|
|
8
|
+
"include": ["src/**/*.ts", "test/**/*.ts"]
|
|
21
9
|
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
2
|
+
import angular from '@analogjs/vite-plugin-angular';
|
|
3
|
+
import { defineConfig } from 'vite';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
// disableTypeChecking:false turns Angular semantic diagnostics (NG2007 etc.)
|
|
7
|
+
// back on for specs — otherwise only the ng-packagr build typechecks src, and
|
|
8
|
+
// spec files get no type checking at all.
|
|
9
|
+
plugins: [angular({ disableTypeChecking: false })],
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
environment: 'jsdom',
|
|
13
|
+
setupFiles: ['test/setup.ts'],
|
|
14
|
+
include: ['test/**/*.spec.ts'],
|
|
15
|
+
pool: 'forks',
|
|
16
|
+
coverage: {
|
|
17
|
+
provider: 'istanbul',
|
|
18
|
+
reportsDirectory: 'coverage',
|
|
19
|
+
reporter: ['lcov', 'text-summary'],
|
|
20
|
+
include: ['src/**/*.ts'],
|
|
21
|
+
// Coverage floor — fails the run when coverage drops below these numbers.
|
|
22
|
+
thresholds: {
|
|
23
|
+
statements: 85,
|
|
24
|
+
branches: 75,
|
|
25
|
+
functions: 85,
|
|
26
|
+
lines: 85,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
package/angular.json
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
-
"version": 1,
|
|
4
|
-
"newProjectRoot": "projects",
|
|
5
|
-
"projects": {
|
|
6
|
-
"dynamic-field-kit-angular": {
|
|
7
|
-
"projectType": "library",
|
|
8
|
-
"root": "",
|
|
9
|
-
"sourceRoot": "src",
|
|
10
|
-
"prefix": "dfk",
|
|
11
|
-
"architect": {
|
|
12
|
-
"test": {
|
|
13
|
-
"builder": "@angular-devkit/build-angular:karma",
|
|
14
|
-
"options": {
|
|
15
|
-
"main": "test.ts",
|
|
16
|
-
"polyfills": ["zone.js"],
|
|
17
|
-
"tsConfig": "tsconfig.spec.json",
|
|
18
|
-
"karmaConfig": "karma.conf.js",
|
|
19
|
-
"assets": [],
|
|
20
|
-
"styles": [],
|
|
21
|
-
"scripts": []
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
package/karma.conf.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module.exports = function (config) {
|
|
2
|
-
config.set({
|
|
3
|
-
basePath: '',
|
|
4
|
-
frameworks: ['jasmine', 'karma-typescript'],
|
|
5
|
-
plugins: [
|
|
6
|
-
'karma-jasmine',
|
|
7
|
-
'karma-chrome-launcher',
|
|
8
|
-
'karma-typescript',
|
|
9
|
-
'karma-coverage',
|
|
10
|
-
],
|
|
11
|
-
files: [{ pattern: 'test/**/*.spec.ts', included: true }],
|
|
12
|
-
preprocessors: {
|
|
13
|
-
'test/**/*.spec.ts': ['karma-typescript'],
|
|
14
|
-
},
|
|
15
|
-
client: {
|
|
16
|
-
jasmine: {},
|
|
17
|
-
},
|
|
18
|
-
karmaTypescriptConfig: {
|
|
19
|
-
tsconfig: './tsconfig.spec.json',
|
|
20
|
-
},
|
|
21
|
-
reporters: ['progress', 'coverage'],
|
|
22
|
-
browsers: ['ChromeHeadless'],
|
|
23
|
-
customLaunchers: {
|
|
24
|
-
ChromeHeadlessCI: {
|
|
25
|
-
base: 'ChromeHeadless',
|
|
26
|
-
flags: ['--no-sandbox', '--disable-gpu'],
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
singleRun: true,
|
|
30
|
-
coverageReporter: {
|
|
31
|
-
reporters: [
|
|
32
|
-
{ type: 'lcov', dir: 'coverage', subdir: '.' },
|
|
33
|
-
{ type: 'text-summary' },
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
};
|
package/test/angular.spec.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
describe('Angular Package', () => {
|
|
2
|
-
describe('Components', () => {
|
|
3
|
-
it('should have DynamicInput component', () => {
|
|
4
|
-
expect(true).toBe(true);
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
it('should have FieldInput component', () => {
|
|
8
|
-
expect(true).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should have MultiFieldInput component', () => {
|
|
12
|
-
expect(true).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('Field Description', () => {
|
|
17
|
-
it('should create field with name and type', () => {
|
|
18
|
-
const field = { name: 'username', type: 'text' };
|
|
19
|
-
expect(field.name).toBe('username');
|
|
20
|
-
expect(field.type).toBe('text');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should support optional label', () => {
|
|
24
|
-
const field = { name: 'email', type: 'text', label: 'Email' };
|
|
25
|
-
expect(field.label).toBe('Email');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should support placeholder', () => {
|
|
29
|
-
const field = { name: 'name', type: 'text', placeholder: 'Enter name' };
|
|
30
|
-
expect(field.placeholder).toBe('Enter name');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should support required flag', () => {
|
|
34
|
-
const field = { name: 'email', type: 'text', required: true };
|
|
35
|
-
expect(field.required).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should support options for select fields', () => {
|
|
39
|
-
const field = {
|
|
40
|
-
name: 'country',
|
|
41
|
-
type: 'text',
|
|
42
|
-
options: [{ label: 'Vietnam', value: 'vn' }],
|
|
43
|
-
};
|
|
44
|
-
expect(field.options?.length).toBe(1);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should support className', () => {
|
|
48
|
-
const field = { name: 'test', type: 'text', className: 'custom-class' };
|
|
49
|
-
expect(field.className).toBe('custom-class');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should support description', () => {
|
|
53
|
-
const field = { name: 'test', type: 'text', description: 'Help text' };
|
|
54
|
-
expect(field.description).toBe('Help text');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('Layouts', () => {
|
|
59
|
-
it('should support row layout', () => {
|
|
60
|
-
expect('row').toBe('row');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should support grid layout', () => {
|
|
64
|
-
expect('grid').toBe('grid');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should support column layout', () => {
|
|
68
|
-
expect('column').toBe('column');
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('appearCondition', () => {
|
|
73
|
-
it('should evaluate appearCondition', () => {
|
|
74
|
-
const condition = (data: any) => data.show === true;
|
|
75
|
-
expect(condition({ show: true })).toBe(true);
|
|
76
|
-
expect(condition({ show: false })).toBe(false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should filter fields based on appearCondition', () => {
|
|
80
|
-
const fields = [
|
|
81
|
-
{ name: 'alwaysShow', type: 'text' },
|
|
82
|
-
{
|
|
83
|
-
name: 'conditional',
|
|
84
|
-
type: 'text',
|
|
85
|
-
appearCondition: (data: any) => data.accountType === 'business',
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
const visibleForPersonal = fields.filter(
|
|
90
|
-
(f) =>
|
|
91
|
-
!f.appearCondition || f.appearCondition({ accountType: 'personal' })
|
|
92
|
-
);
|
|
93
|
-
expect(visibleForPersonal.length).toBe(1);
|
|
94
|
-
|
|
95
|
-
const visibleForBusiness = fields.filter(
|
|
96
|
-
(f) =>
|
|
97
|
-
!f.appearCondition || f.appearCondition({ accountType: 'business' })
|
|
98
|
-
);
|
|
99
|
-
expect(visibleForBusiness.length).toBe(2);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|
package/test/integration.spec.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
describe('Integration: Form with multiple fields', () => {
|
|
2
|
-
it('should create field descriptions', () => {
|
|
3
|
-
const fields = [
|
|
4
|
-
{ name: 'name', type: 'text', label: 'Full Name' },
|
|
5
|
-
{ name: 'email', type: 'text', label: 'Email' },
|
|
6
|
-
];
|
|
7
|
-
expect(fields.length).toBe(2);
|
|
8
|
-
expect(fields[0].label).toBe('Full Name');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should handle form data', () => {
|
|
12
|
-
const formData = { name: 'John', email: 'john@example.com' };
|
|
13
|
-
expect(formData.name).toBe('John');
|
|
14
|
-
expect(formData.email).toBe('john@example.com');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should evaluate appearCondition', () => {
|
|
18
|
-
const condition = (data: any) => data.accountType === 'business';
|
|
19
|
-
expect(condition({ accountType: 'personal' })).toBe(false);
|
|
20
|
-
expect(condition({ accountType: 'business' })).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('FieldDescription', () => {
|
|
25
|
-
it('should have name and type', () => {
|
|
26
|
-
const field = { name: 'username', type: 'text' };
|
|
27
|
-
expect(field.name).toBe('username');
|
|
28
|
-
expect(field.type).toBe('text');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should support optional properties', () => {
|
|
32
|
-
const field = {
|
|
33
|
-
name: 'username',
|
|
34
|
-
type: 'text',
|
|
35
|
-
label: 'Username',
|
|
36
|
-
placeholder: 'Enter name',
|
|
37
|
-
required: true,
|
|
38
|
-
options: [{ label: 'Option 1' }],
|
|
39
|
-
};
|
|
40
|
-
expect(field.label).toBe('Username');
|
|
41
|
-
expect(field.required).toBe(true);
|
|
42
|
-
expect(field.options?.length).toBe(1);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('Properties', () => {
|
|
47
|
-
it('should be a record of key-value pairs', () => {
|
|
48
|
-
const props: Record<string, any> = {
|
|
49
|
-
username: 'john',
|
|
50
|
-
age: 25,
|
|
51
|
-
active: true,
|
|
52
|
-
};
|
|
53
|
-
expect(props.username).toBe('john');
|
|
54
|
-
expect(props.age).toBe(25);
|
|
55
|
-
expect(props.active).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
});
|
package/test/layouts.spec.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
describe('Layout: Row', () => {
|
|
2
|
-
it('should render fields in row layout', () => {
|
|
3
|
-
const layout = 'row';
|
|
4
|
-
expect(layout).toBe('row');
|
|
5
|
-
});
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
describe('Layout: Grid', () => {
|
|
9
|
-
it('should render fields in default grid layout', () => {
|
|
10
|
-
const layout = 'grid';
|
|
11
|
-
expect(layout).toBe('grid');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should support grid with columns option', () => {
|
|
15
|
-
const layout = { type: 'grid', columns: 3 };
|
|
16
|
-
expect(layout.type).toBe('grid');
|
|
17
|
-
expect(layout.columns).toBe(3);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('Layout: Column', () => {
|
|
22
|
-
it('should render fields in column layout', () => {
|
|
23
|
-
const layout = 'column';
|
|
24
|
-
expect(layout).toBe('column');
|
|
25
|
-
});
|
|
26
|
-
});
|