@dsivd/prestations-ng 19.2.0-beta.1 → 19.2.0-beta.10
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 +182 -0
- package/CONTRIBUTING.md +43 -3
- package/GOOD_PRACTICES.md +186 -29
- package/UPGRADING_V19.md +181 -1
- package/dsivd-prestations-ng-19.2.0-beta.10.tgz +0 -0
- package/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/eslint/configs/template-recommended.mjs +2 -0
- package/eslint/configs/template-rules.mjs +6 -2
- package/eslint/configs/ts-recommended.mjs +2 -0
- package/eslint/configs/ts-rules.mjs +6 -2
- package/eslint/index.mjs +17 -0
- package/eslint/rules/no-direct-signal-mutation.mjs +1 -1
- package/fesm2022/dsivd-prestations-ng.mjs +1626 -1142
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -2
- package/schematics/ng-add/index.js +2 -4
- package/schematics/ng-update/migrations/update-13_2_0/index.js +7 -2
- package/schematics/utility/add-allowed-common-js-dependencies.js +39 -29
- package/schematics/utility/remove-emit-decorator-metadata.js +8 -9
- package/src/eslint/configs/__tests__/configs.test.mjs +46 -2
- package/src/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/src/eslint/configs/template-recommended.mjs +2 -0
- package/src/eslint/configs/template-rules.mjs +6 -2
- package/src/eslint/configs/ts-recommended.mjs +2 -0
- package/src/eslint/configs/ts-rules.mjs +6 -2
- package/src/eslint/index.mjs +17 -0
- package/src/eslint/rules/no-direct-signal-mutation.mjs +1 -1
- package/types/dsivd-prestations-ng.d.ts +303 -147
- package/dsivd-prestations-ng-19.2.0-beta.1.tgz +0 -0
package/UPGRADING_V19.md
CHANGED
|
@@ -1064,6 +1064,8 @@ npm install --save-dev eslint-plugin-simple-import-sort@13
|
|
|
1064
1064
|
npm install --save-dev eslint-config-prettier@10
|
|
1065
1065
|
npm install --save-dev eslint-plugin-import-x@4
|
|
1066
1066
|
npm install --save-dev prettier
|
|
1067
|
+
# only if your project has Vitest specs (see "Migrate to vitest")
|
|
1068
|
+
npm install --save-dev @vitest/eslint-plugin@1
|
|
1067
1069
|
```
|
|
1068
1070
|
|
|
1069
1071
|
In your `front` folder, add a `.prettierrc` file with the following content:
|
|
@@ -1140,6 +1142,12 @@ export default tseslint.config(
|
|
|
1140
1142
|
extends: [...prestationsNg.configs.templateRecommended],
|
|
1141
1143
|
},
|
|
1142
1144
|
|
|
1145
|
+
// ─── Vitest specs (only if your project has Vitest specs) ─────────────────
|
|
1146
|
+
{
|
|
1147
|
+
files: ['**/*.spec.ts'],
|
|
1148
|
+
extends: [...prestationsNg.configs.specTsRecommended],
|
|
1149
|
+
},
|
|
1150
|
+
|
|
1143
1151
|
// ─── Prettier (must be last — disables formatting rules) ───────────────────
|
|
1144
1152
|
prettier,
|
|
1145
1153
|
);
|
|
@@ -1150,6 +1158,11 @@ everything: the `eslint`, `typescript-eslint` and `angular-eslint` baselines, th
|
|
|
1150
1158
|
accessibility set, the naming and import conventions, and the rules written by the library
|
|
1151
1159
|
itself. You declare no `plugins` and list no rule.
|
|
1152
1160
|
|
|
1161
|
+
`specTsRecommended` is the `recommended` config of `@vitest/eslint-plugin`, which also counts the assertions
|
|
1162
|
+
made by the `HttpTestingController` (`expectOne`, `expectNone`, `verify`) and by helpers named `expect*`,
|
|
1163
|
+
`assert*` or `test*`. The `**/*.spec.ts` pattern does not reach the Cypress `*.spec.cy.ts` files. It needs
|
|
1164
|
+
`@vitest/eslint-plugin` only when you use it: the other configs work without it.
|
|
1165
|
+
|
|
1153
1166
|
The packages installed above are still required — the library's config imports them from your
|
|
1154
1167
|
`node_modules` rather than shipping its own copies. For the same reason, do **not** spread
|
|
1155
1168
|
`angular.configs.tsRecommended` or `angular.configs.templateRecommended` yourself: they are
|
|
@@ -1449,6 +1462,172 @@ npm run test
|
|
|
1449
1462
|
npm start
|
|
1450
1463
|
```
|
|
1451
1464
|
|
|
1465
|
+
### `foehn-list-summary` is now generic (since 19.2.0)
|
|
1466
|
+
|
|
1467
|
+
`FoehnListSummaryComponent` is now `FoehnListSummaryComponent<T extends FoehnListItem>` and `FoehnListItemDescription` now takes a type parameter: `FoehnListItemDescription<T>`.
|
|
1468
|
+
Using `FoehnListItemDescription` without its type parameter no longer compiles.
|
|
1469
|
+
|
|
1470
|
+
Replace every `FoehnListItemDescription` with `FoehnListItemDescription<MyItem>`, where `MyItem` is your class extending `FoehnListItem`. You can then drop the casts (or `any`) in `getFormattedValue` and `hideIfEmpty`:
|
|
1471
|
+
|
|
1472
|
+
Before:
|
|
1473
|
+
|
|
1474
|
+
```typescript
|
|
1475
|
+
getListItemsDescription(): FoehnListItemDescription[] {
|
|
1476
|
+
return [
|
|
1477
|
+
{
|
|
1478
|
+
label: 'Name',
|
|
1479
|
+
getFormattedValue: (item: FoehnListItem) =>
|
|
1480
|
+
(item as MyItem).name ?? '',
|
|
1481
|
+
},
|
|
1482
|
+
];
|
|
1483
|
+
}
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
After:
|
|
1487
|
+
|
|
1488
|
+
```typescript
|
|
1489
|
+
getListItemsDescription(): FoehnListItemDescription<MyItem>[] {
|
|
1490
|
+
return [
|
|
1491
|
+
{
|
|
1492
|
+
label: 'Name',
|
|
1493
|
+
getFormattedValue: (item: MyItem) => item.name ?? '',
|
|
1494
|
+
},
|
|
1495
|
+
];
|
|
1496
|
+
}
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
The same applies to the function passed to `getListItemTitle`, which now receives a `MyItem`.
|
|
1500
|
+
|
|
1501
|
+
### `core-js` is no longer needed (since 19.2.0)
|
|
1502
|
+
|
|
1503
|
+
prestations-ng no longer imports `core-js` and no longer has it as a peer dependency. If your project does not
|
|
1504
|
+
import `core-js` itself, uninstall it:
|
|
1505
|
+
|
|
1506
|
+
```bash
|
|
1507
|
+
npm uninstall core-js
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
Then, in your `angular.json` file, remove `"core-js/modules/es.array.includes"` from `"allowedCommonJsDependencies"`.
|
|
1511
|
+
The CommonJS dependencies of prestations-ng are now `"iban"` and `"dayjs"` (which also covers `dayjs/plugin/*` and
|
|
1512
|
+
`dayjs/locale/*`):
|
|
1513
|
+
|
|
1514
|
+
```json
|
|
1515
|
+
"allowedCommonJsDependencies": ["iban", "dayjs"]
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
### Zoneless (optional but recommended)
|
|
1519
|
+
|
|
1520
|
+
Since v19.2, prestations-ng does not need zone.js anymore: the state of its components is held by signals, which
|
|
1521
|
+
tell Angular when a component has to be refreshed. prestations-ng keeps working in an application using zone.js,
|
|
1522
|
+
so this step is optional, but a zoneless application is lighter, faster and easier to debug. See the
|
|
1523
|
+
[Angular zoneless guide](https://angular.dev/guide/zoneless).
|
|
1524
|
+
|
|
1525
|
+
In any case, if your code reads some fields of the prestations-ng components (e.g. through `viewChild()` or in
|
|
1526
|
+
your tests), check the fields that are now signals in the [CHANGELOG](CHANGELOG.md#1920---should-be-aligned-with-prestations-be-192x).
|
|
1527
|
+
|
|
1528
|
+
#### Enable the zoneless change detection
|
|
1529
|
+
|
|
1530
|
+
Zoneless is the default since Angular 21: `ng update` added `provideZoneChangeDetection()` to keep zone.js in your
|
|
1531
|
+
application. Replace it in your `app.config.ts` (`providePrestationsNgCore()` already provides
|
|
1532
|
+
`provideBrowserGlobalErrorListeners()`, which reports to your `ErrorHandler` the errors zone.js used to catch):
|
|
1533
|
+
|
|
1534
|
+
```diff
|
|
1535
|
+
export const appConfig: ApplicationConfig = {
|
|
1536
|
+
providers: [
|
|
1537
|
+
- provideZoneChangeDetection({ eventCoalescing: true }),
|
|
1538
|
+
+ provideZonelessChangeDetection(),
|
|
1539
|
+
providePrestationsNgCore(),
|
|
1540
|
+
...
|
|
1541
|
+
],
|
|
1542
|
+
};
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
Remove `zone.js` and `zone.js/testing` from the `polyfills` of the `build` and `test` targets in your
|
|
1546
|
+
`angular.json` (and from your `polyfills.ts` file if you still have one): without them, the `TestBed` is zoneless
|
|
1547
|
+
too.
|
|
1548
|
+
|
|
1549
|
+
```diff
|
|
1550
|
+
"polyfills": [
|
|
1551
|
+
- "zone.js"
|
|
1552
|
+
],
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
then uninstall zone.js:
|
|
1556
|
+
|
|
1557
|
+
```bash
|
|
1558
|
+
npm uninstall zone.js
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
#### Check your components
|
|
1562
|
+
|
|
1563
|
+
Without zone.js, Angular refreshes a component only when it is notified that something changed:
|
|
1564
|
+
|
|
1565
|
+
- a listener of its template has been called (`(click)`, `(modelChange)`, `(userInput)`...), including the
|
|
1566
|
+
`[(model)]` two-way bindings
|
|
1567
|
+
- a signal read by its template has changed (`signal()`, `computed()`, `input()`, `model()`, `toSignal()`...)
|
|
1568
|
+
- `ChangeDetectorRef.markForCheck()` has been called, which is what the `async` pipe does when an observable emits
|
|
1569
|
+
|
|
1570
|
+
A field modified elsewhere (in a `subscribe()`, a `setTimeout()`, a `Promise`, a callback...) is not displayed
|
|
1571
|
+
anymore until something else refreshes the component. Hold it in a signal (see
|
|
1572
|
+
[INTRODUCTION_ANGULAR_SIGNALS.md](INTRODUCTION_ANGULAR_SIGNALS.md)):
|
|
1573
|
+
|
|
1574
|
+
```diff
|
|
1575
|
+
- isLoading = true;
|
|
1576
|
+
+ readonly isLoading = signal(true);
|
|
1577
|
+
|
|
1578
|
+
ngOnInit(): void {
|
|
1579
|
+
this.myService.load().subscribe(() => {
|
|
1580
|
+
- this.isLoading = false;
|
|
1581
|
+
+ this.isLoading.set(false);
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
```diff
|
|
1587
|
+
- @if (isLoading) {
|
|
1588
|
+
+ @if (isLoading()) {
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
Pay attention to:
|
|
1592
|
+
|
|
1593
|
+
- your pages (`AbstractPageComponent` and its subclasses): prestations-ng refreshes them when it sets `form` and
|
|
1594
|
+
`reference`. But if you modify the form yourself in an asynchronous callback, e.g. the one given to
|
|
1595
|
+
`SessionInfoWithApplicationService.prefillForm()` or a `subscribe()` of your own service, call
|
|
1596
|
+
`markForCheck()` afterwards:
|
|
1597
|
+
|
|
1598
|
+
```ts
|
|
1599
|
+
private readonly changeDetectorRef = inject(ChangeDetectorRef);
|
|
1600
|
+
|
|
1601
|
+
ngOnInit(): void {
|
|
1602
|
+
super.ngOnInit();
|
|
1603
|
+
this.sessionInfoWithApplicationService.prefillForm((info) => {
|
|
1604
|
+
this.form.email = info?.email ?? null;
|
|
1605
|
+
this.changeDetectorRef.markForCheck();
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
- `NgZone`: `onStable`, `onMicrotaskEmpty` and `onUnstable` never emit without zone.js, and `run()` or
|
|
1611
|
+
`runOutsideAngular()` are useless. To act on the rendered DOM (focus, scroll...), replace them, as well as the
|
|
1612
|
+
`setTimeout()` waiting for the view, by `afterNextRender()`. `AbstractMenuPageComponent.ngZone` is deprecated.
|
|
1613
|
+
|
|
1614
|
+
```diff
|
|
1615
|
+
- this.ngZone.onStable.pipe(first()).subscribe(() => this.focusFirstField());
|
|
1616
|
+
+ afterNextRender(() => this.focusFirstField(), { injector: this.injector });
|
|
1617
|
+
```
|
|
1618
|
+
|
|
1619
|
+
#### Check your tests
|
|
1620
|
+
|
|
1621
|
+
- `fakeAsync()`, `tick()`, `flush()` and `waitForAsync()` need zone.js: write `async` tests, with
|
|
1622
|
+
`vi.useFakeTimers()` and `await vi.advanceTimersByTimeAsync(ms)` to control the timers (see
|
|
1623
|
+
[Migrate to vitest](#migrate-to-vitest)).
|
|
1624
|
+
- `fixture.detectChanges()` only refreshes the views which have been notified, then checks that nothing changed
|
|
1625
|
+
meanwhile: a field modified without notification makes the test fail with the error `NG0100`
|
|
1626
|
+
(`ExpressionChangedAfterItHasBeenCheckedError`). Fix the component (signal, `markForCheck()`) rather than the
|
|
1627
|
+
test.
|
|
1628
|
+
- `await fixture.whenStable()` waits for the change detection and for the pending tasks of the application
|
|
1629
|
+
(e.g. the debounced `userInput` of the prestations-ng inputs), not for the timers.
|
|
1630
|
+
|
|
1452
1631
|
### Last but not least, check if your application is working!!!
|
|
1453
1632
|
|
|
1454
1633
|
#### Be sure your application gets a fresh start
|
|
@@ -1488,7 +1667,8 @@ See [getImtPortailApplications](https://dsigit.etat-de-vaud.ch/outils/git/projec
|
|
|
1488
1667
|
'project': 'cybersdk',
|
|
1489
1668
|
'name' : 'skeleton',
|
|
1490
1669
|
'gitUrl' : 'ssh://git@bitbucket.etat-de-vaud.ch/cybsdk/skeleton.git',
|
|
1491
|
-
|
|
1670
|
+
- 'jdk' : '21',
|
|
1671
|
+
+ 'jdk' : '25',
|
|
1492
1672
|
- 'node' : '20'
|
|
1493
1673
|
+ 'node' : '22'
|
|
1494
1674
|
],
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The house preset of the Vitest specs: the recommended rules of `@vitest/eslint-plugin`, with the
|
|
3
|
+
* assertions also made by the helpers of the specs and by the `HttpTestingController`.
|
|
4
|
+
*
|
|
5
|
+
* Meant for the `extends` of a `**\/*.spec.ts` block (the Cypress `*.spec.cy.ts` files are not
|
|
6
|
+
* Vitest specs).
|
|
7
|
+
*
|
|
8
|
+
* @param {import('@vitest/eslint-plugin').default} vitest the `@vitest/eslint-plugin` module
|
|
9
|
+
* @returns {import('typescript-eslint').ConfigArray}
|
|
10
|
+
*/
|
|
11
|
+
const specTsRecommended = (vitest) => [
|
|
12
|
+
vitest.configs.recommended,
|
|
13
|
+
{
|
|
14
|
+
name: 'prestations-ng/spec-ts-recommended',
|
|
15
|
+
rules: {
|
|
16
|
+
'vitest/expect-expect': [
|
|
17
|
+
'error',
|
|
18
|
+
{
|
|
19
|
+
assertFunctionNames: [
|
|
20
|
+
'expect',
|
|
21
|
+
'expect*',
|
|
22
|
+
'assert*',
|
|
23
|
+
'test*',
|
|
24
|
+
'*.expectOne',
|
|
25
|
+
'*.expectNone',
|
|
26
|
+
'*.verify',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export default specTsRecommended;
|
|
@@ -8,6 +8,8 @@ import templateRules from './template-rules.mjs';
|
|
|
8
8
|
*
|
|
9
9
|
* Meant for the `extends` of a `**\/*.html` block. It brings its own parser and plugins,
|
|
10
10
|
* so do not spread `angular.configs.templateRecommended` alongside it.
|
|
11
|
+
*
|
|
12
|
+
* @returns {import('typescript-eslint').ConfigArray}
|
|
11
13
|
*/
|
|
12
14
|
const templateRecommended = (plugin) => [
|
|
13
15
|
...angular.configs.templateRecommended,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import templateBase from './template-base.mjs';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Only the rules written by this library. `templateRecommended` includes it, alongside
|
|
5
|
+
* the house style; use this layer directly to opt out of the latter.
|
|
6
|
+
*
|
|
7
|
+
* @returns {import('typescript-eslint').ConfigArray}
|
|
8
|
+
*/
|
|
5
9
|
const templateRules = (plugin) => [
|
|
6
10
|
templateBase(plugin),
|
|
7
11
|
{
|
|
@@ -14,6 +14,8 @@ import tsRules from './ts-rules.mjs';
|
|
|
14
14
|
* Meant for the `extends` of a `**\/*.ts` block. It brings its own parser and plugins,
|
|
15
15
|
* so do not spread `angular.configs.tsRecommended` alongside it — declaring the same
|
|
16
16
|
* plugin twice from two different copies makes ESLint fail outright.
|
|
17
|
+
*
|
|
18
|
+
* @returns {import('typescript-eslint').ConfigArray}
|
|
17
19
|
*/
|
|
18
20
|
const tsRecommended = (plugin) => [
|
|
19
21
|
eslint.configs.recommended,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import tsBase from './ts-base.mjs';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Only the rules written by this library. `tsRecommended` includes it, alongside the
|
|
5
|
+
* house style; use this layer directly to opt out of the latter.
|
|
6
|
+
*
|
|
7
|
+
* @returns {import('typescript-eslint').ConfigArray}
|
|
8
|
+
*/
|
|
5
9
|
const tsRules = (plugin) => [
|
|
6
10
|
tsBase(plugin),
|
|
7
11
|
{
|
package/eslint/index.mjs
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import specTsRecommended from './configs/spec-ts-recommended.mjs';
|
|
1
2
|
import templateRecommended from './configs/template-recommended.mjs';
|
|
2
3
|
import templateRules from './configs/template-rules.mjs';
|
|
3
4
|
import tsRecommended from './configs/ts-recommended.mjs';
|
|
4
5
|
import tsRules from './configs/ts-rules.mjs';
|
|
5
6
|
import rules from './rules/index.mjs';
|
|
6
7
|
|
|
8
|
+
// Only needed by the projects linting Vitest specs: the other configs can be used without it.
|
|
9
|
+
const vitest = await import('@vitest/eslint-plugin').then(
|
|
10
|
+
(module) => module.default,
|
|
11
|
+
() => null,
|
|
12
|
+
);
|
|
13
|
+
const specTsRecommendedConfig = vitest ? specTsRecommended(vitest) : null;
|
|
14
|
+
|
|
7
15
|
const plugin = {
|
|
8
16
|
meta: { name: '@dsivd/prestations-ng' },
|
|
9
17
|
rules,
|
|
@@ -16,6 +24,15 @@ const configs = {
|
|
|
16
24
|
// Only this library's own rules, for a project that supplies its own style.
|
|
17
25
|
tsRules: tsRules(plugin),
|
|
18
26
|
templateRules: templateRules(plugin),
|
|
27
|
+
// The preset of the Vitest specs (needs @vitest/eslint-plugin)
|
|
28
|
+
get specTsRecommended() {
|
|
29
|
+
if (!specTsRecommendedConfig) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'prestationsNg.configs.specTsRecommended needs @vitest/eslint-plugin: npm install --save-dev @vitest/eslint-plugin@1',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return specTsRecommendedConfig;
|
|
35
|
+
},
|
|
19
36
|
};
|
|
20
37
|
|
|
21
38
|
export { configs, plugin, rules };
|
|
@@ -241,7 +241,7 @@ function boundNameSignalKind(identifier, scopeContext, seen) {
|
|
|
241
241
|
scopeContext.sourceCode.getScope(identifier),
|
|
242
242
|
identifier.name,
|
|
243
243
|
);
|
|
244
|
-
if (
|
|
244
|
+
if (variable?.defs.length !== 1 || seen.has(variable)) {
|
|
245
245
|
return undefined;
|
|
246
246
|
}
|
|
247
247
|
seen.add(variable);
|