diff --git a/apps/showcase/src/app/home/home.template.html b/apps/showcase/src/app/home/home.template.html index 2bbc4582c0..66c35b884d 100644 --- a/apps/showcase/src/app/home/home.template.html +++ b/apps/showcase/src/app/home/home.template.html @@ -1,5 +1,5 @@
- +

Easily build a customizable Angular based application

The Otter project is a highly modular framework whose goal is to provide a common platform to accelerate and facilitate the development of Angular based Web Applications.
diff --git a/apps/showcase/src/components/showcase/dynamic-content/dynamic-content-pres.template.html b/apps/showcase/src/components/showcase/dynamic-content/dynamic-content-pres.template.html index 1a52050ac5..bb9eed3961 100644 --- a/apps/showcase/src/components/showcase/dynamic-content/dynamic-content-pres.template.html +++ b/apps/showcase/src/components/showcase/dynamic-content/dynamic-content-pres.template.html @@ -1,7 +1,7 @@
- +
diff --git a/apps/showcase/src/components/showcase/localization/localization-pres.template.html b/apps/showcase/src/components/showcase/localization/localization-pres.template.html index 8208dfcab0..3862d6e379 100644 --- a/apps/showcase/src/components/showcase/localization/localization-pres.template.html +++ b/apps/showcase/src/components/showcase/localization/localization-pres.template.html @@ -8,29 +8,29 @@

@if (form.value.destination) { - {{ translations.welcomeWithCityName | translate: { cityName: (translations.cityName + '.' + form.value.destination) | translate } }} + {{ translations.welcomeWithCityName | o3rTranslate: { cityName: (translations.cityName + '.' + form.value.destination) | o3rTranslate } }} } @else { - {{ translations.welcome | translate }} + {{ translations.welcome | o3rTranslate }} }

-

{{ translations.question | translate }}

+

{{ translations.question | o3rTranslate }}

- +
- +
diff --git a/apps/showcase/src/components/showcase/rules-engine/rules-engine-pres.template.html b/apps/showcase/src/components/showcase/rules-engine/rules-engine-pres.template.html index 80b7961cc8..a4821763d5 100644 --- a/apps/showcase/src/components/showcase/rules-engine/rules-engine-pres.template.html +++ b/apps/showcase/src/components/showcase/rules-engine/rules-engine-pres.template.html @@ -1,30 +1,30 @@
- +

@if (form.value.destination) { - {{ translations.welcomeWithCityName | translate: { cityName: (translations.cityName + '.' + form.value.destination) | translate } }} + {{ translations.welcomeWithCityName | o3rTranslate: { cityName: (translations.cityName + '.' + form.value.destination) | o3rTranslate } }} } @else { - {{ translations.welcome | translate }} + {{ translations.welcome | o3rTranslate }} }

-

{{ translations.question | translate }}

+

{{ translations.question | o3rTranslate }}

- + @@ -32,14 +32,14 @@

{{ translations.question | translate }

- +
@if ((config$ | async)?.shouldProposeRoundTrip) {
- +
diff --git a/docs/components/PLACEHOLDERS.md b/docs/components/PLACEHOLDERS.md index 9d3a16080b..facbe1e35b 100644 --- a/docs/components/PLACEHOLDERS.md +++ b/docs/components/PLACEHOLDERS.md @@ -101,7 +101,7 @@ Your second option is to manage your placeholder in a single template and use th In that use case, you can refer to localization keys in your master placeholder template. The module will then translate the template based on the localization service and keep it updated after every language -change. +change. As your placeholder URL remains the same, it will be updated dynamically without any server call. #### Implementation @@ -139,7 +139,7 @@ Let's consider what this placeholder would look like if it were completely integ ```html " -
{{'o3r-increment-key' | translate}}
" +
{{'o3r-increment-key' | o3rTranslate}}
" ``` Then, let's create a new localization key for each of your supported languages: @@ -161,7 +161,7 @@ Then, let's create a new localization key for each of your supported languages: ``` Note that the ``o3r-increment-key`` translations take ``increment`` as a parameter. This means you need to create -an ``increment`` fact to fill the value. +an ``increment`` fact to fill the value. You can follow the [fact creation documentation](../rules-engine/how-to-use/create-custom-fact.md). ```typescript @@ -188,7 +188,7 @@ export class PageFactsService extends FactsService { } ``` -Once the translation keys and the referenced fact exist, you can link them to your placeholder. +Once the translation keys and the referenced fact exist, you can link them to your placeholder. See how the original translation pipe has been replaced and how the localization key is bound to the ``increment`` fact: ```json @@ -269,5 +269,5 @@ You will probably want to reuse your placeholder in different pages for differen You might be tempted to use this generic template for all your events but the value of your counter parameter will depend on the event itself (Easter or next Summer Holidays for example). This means that ``increment`` might have a different value depending on the context of the page which might be tricky to -maintain and to debug. +maintain and to debug. Try to keep it as simple as possible. diff --git a/docs/dynamic-content/DYNAMIC_CONTENT.md b/docs/dynamic-content/DYNAMIC_CONTENT.md index ae738e7b95..09f5540d20 100644 --- a/docs/dynamic-content/DYNAMIC_CONTENT.md +++ b/docs/dynamic-content/DYNAMIC_CONTENT.md @@ -16,8 +16,8 @@ The module provides two things: A pipe to be used in your component templates: ```html - or - + or + ``` and a service to be used in your component classes, for example: diff --git a/docs/forms/FORM_ERRORS.md b/docs/forms/FORM_ERRORS.md index 55c338dc53..1f348006a3 100644 --- a/docs/forms/FORM_ERRORS.md +++ b/docs/forms/FORM_ERRORS.md @@ -311,7 +311,7 @@ This is only an example of implementation. The _translationKey_ and _translation // use the translation object for the translationKey and get the translationParams from the error object returned by 'date-inline-input'. - {{translations.maxMonthInDate | translate: {max: travelerForm.controls.dateOfBirth.errors?.max.max} }} + {{translations.maxMonthInDate | o3rTranslate: {max: travelerForm.controls.dateOfBirth.errors?.max.max} }} ``` @@ -324,7 +324,7 @@ This is only an example of implementation. The _translationKey_ and _translation // translation key and params are already accessible in the error object returned by the custom validator - {{customError.translationKey | translate: customError.translationParams }} + {{customError.translationKey | o3rTranslate: customError.translationParams }} ``` diff --git a/docs/localization/LOCALIZATION.md b/docs/localization/LOCALIZATION.md index dc3b182379..94efc71460 100644 --- a/docs/localization/LOCALIZATION.md +++ b/docs/localization/LOCALIZATION.md @@ -204,7 +204,7 @@ export class SimpleHeaderPresModule {} -{{ "o3r-simple-header-pres.motto" | translate }} // => this will output Let's shape the future of travel +{{ "o3r-simple-header-pres.motto" | o3rTranslate }} // => this will output Let's shape the future of travel
// => this will output Hello, otter friend! @@ -221,7 +221,7 @@ export class SimpleHeaderPresModule {} - + ``` @@ -233,7 +233,7 @@ As a result "**hello bold**" will be printed inside the span element. -{{ "someBagsAdded" | translate:{bags: 5} }} // => will output "You have added 5 bags" +{{ "someBagsAdded" | o3rTranslate:{bags: 5} }} // => will output "You have added 5 bags" ``` @@ -485,7 +485,7 @@ import {MESSAGE_FORMAT_CONFIG} from 'ngx-translate-messageformat-compiler'; ```typescript // component html template ... - {{translations.nbOfErrors | translate: {count: countMessages} }} + {{translations.nbOfErrors | o3rTranslate: {count: countMessages} }} ... ``` The value of _translations.nbOfErrors_ is the translation key 'o3r-list-inline-messages-pres.nbOfErrors'. The next step translates the key passing some parameters to translate pipe. @@ -508,9 +508,9 @@ Sometimes you may want to display a different resource based on some property va ```typescript // in component html
    -
  • {{ translations.people | translate: { gender: 'female', how: 'influential' } }}
  • -
  • {{ translations.people | translate: { gender: 'male', how: 'funny' } }}
  • -
  • {{ translations.people | translate: { how: 'affectionate' } }}
  • +
  • {{ translations.people | o3rTranslate: { gender: 'female', how: 'influential' } }}
  • +
  • {{ translations.people | o3rTranslate: { gender: 'male', how: 'funny' } }}
  • +
  • {{ translations.people | o3rTranslate: { how: 'affectionate' } }}
``` Note again that _translations.people_ matches _global.people_ key diff --git a/packages/@o3r/components/migration.json b/packages/@o3r/components/migration.json new file mode 100644 index 0000000000..62836c1d0a --- /dev/null +++ b/packages/@o3r/components/migration.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular_devkit/schematics/collection-schema.json", + "schematics": { + "migration-v10_0": { + "version": "10.0.0-alpha.0", + "description": "Updates of the Otter Library to v10.0.*", + "factory": "./schematics/ng-update/v10-0/index#updateV10_0" + } + } +} diff --git a/packages/@o3r/components/package.json b/packages/@o3r/components/package.json index 88385096c3..37b07e80d7 100644 --- a/packages/@o3r/components/package.json +++ b/packages/@o3r/components/package.json @@ -17,7 +17,7 @@ "nx": "nx", "ng": "yarn nx", "copy:schemas": "yarn cpy 'schemas/*.json' dist/schemas", - "prepare:build:builders": "yarn cpy 'builders/**/*.json' dist/builders && yarn cpy 'schematics/**/*.json' 'schematics/**/templates/**' dist/schematics && yarn cpy '{builders,collection}.json' dist && yarn copy:schemas", + "prepare:build:builders": "yarn cpy 'builders/**/*.json' dist/builders && yarn cpy 'schematics/**/*.json' 'schematics/**/templates/**' dist/schematics && yarn cpy '{builders,collection,migration}.json' dist && yarn copy:schemas", "prepare:publish": "prepare-publish ./dist", "prepare:compile": "cp-package-json", "build:builders": "tsc -b tsconfig.builders.json --pretty && yarn generate-cjs-manifest", diff --git a/packages/@o3r/components/schematics/ng-update/v10-0/index.spec.ts b/packages/@o3r/components/schematics/ng-update/v10-0/index.spec.ts new file mode 100644 index 0000000000..6d0f1acddd --- /dev/null +++ b/packages/@o3r/components/schematics/ng-update/v10-0/index.spec.ts @@ -0,0 +1,77 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const migrationPath = path.join(__dirname, '..', '..', '..', 'migration.json'); + + +describe('Update v10', () => { + describe('Update pipes', () => { + let initialTree: Tree; + let runner: SchematicTestRunner; + beforeEach(() => { + initialTree = Tree.empty(); + initialTree.create('angular.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'angular.mocks.json'))); + initialTree.create('package.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'package.mocks.json'))); + initialTree.create('.eslintrc.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', '__dot__eslintrc.mocks.json'))); + initialTree.create('src/components/example.template.html', '{{ 120 | duration }}'); + runner = new SchematicTestRunner('schematics', migrationPath); + }); + + it('should replace the pipe with standalone component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + import { DurationPipeModule } from '@o3r/components'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + standalone: true, + imports: [DurationPipeModule], + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.component.ts')).toMatch('O3rDurationPipe'); + expect(tree.readText('src/components/example.component.ts')).not.toMatch('DurationPipeModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rDuration'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| duration'); + }); + + it('should replace the pipe with module based component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + initialTree.create('src/components/example.module.ts', ` + import { NgModule } from '@angular/core'; + import { DurationPipeModule } from '@o3r/components'; + import { ExampleComponent } from './example.component'; + + @NgModule({ + imports: [DurationPipeModule], + declarations: [ExampleComponent], + exports: [ExampleComponent] + }) + export class ExampleModule {} + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.module.ts')).toMatch('O3rDurationPipe'); + expect(tree.readText('src/components/example.module.ts')).not.toMatch('DurationPipeModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rDuration'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| duration'); + }); + }); +}); diff --git a/packages/@o3r/components/schematics/ng-update/v10-0/index.ts b/packages/@o3r/components/schematics/ng-update/v10-0/index.ts new file mode 100644 index 0000000000..f788854bbc --- /dev/null +++ b/packages/@o3r/components/schematics/ng-update/v10-0/index.ts @@ -0,0 +1,53 @@ +/* eslint-disable camelcase, @typescript-eslint/naming-convention */ +import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { createSchematicWithMetricsIfInstalled, PipeReplacementInfo, updatePipes } from '@o3r/schematics'; + +const pipeReplacementInfo: PipeReplacementInfo = { + capitalize: { + new: { + name: 'o3rCapitalize', + import: 'O3rCapitalizePipe' + }, + import: 'CapitalizePipeModule' + }, + duration: { + new: { + name: 'o3rDuration', + import: 'O3rDurationPipe' + }, + import: 'DurationPipeModule' + }, + keepWhiteSpace: { + new: { + name: 'o3rKeepWhiteSpace', + import: 'O3rKeepWhiteSpacePipe' + }, + import: 'KeepWhiteSpacePipeModule' + }, + replaceWithBold: { + new: { + name: 'o3rReplaceWithBold', + import: 'O3rReplaceWithBoldPipe' + }, + import: 'ReplaceWithBoldPipeModule' + } +}; + +/** + * Update of Otter library V10.0 + */ +function updateV10_0Fn(): Rule { + return (tree: Tree, context: SchematicContext) => { + + const updateRules: Rule[] = [ + updatePipes(pipeReplacementInfo) + ]; + + return chain(updateRules)(tree, context); + }; +} + +/** + * Update of Otter library V10.0 + */ +export const updateV10_0 = createSchematicWithMetricsIfInstalled(updateV10_0Fn); diff --git a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.module.ts b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.module.ts index ca93c01925..dbd908f6e5 100644 --- a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.module.ts +++ b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.module.ts @@ -1,6 +1,9 @@ import {NgModule} from '@angular/core'; import {CapitalizePipe} from './capitalize.pipe'; +/** + * @deprecated please use O3rCapitalizePipe, will be removed in v12. + */ @NgModule({ declarations: [CapitalizePipe], exports: [CapitalizePipe] diff --git a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.spec.ts b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.spec.ts index 686668c147..a76a026e16 100644 --- a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.spec.ts +++ b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.spec.ts @@ -1,22 +1,28 @@ -import {CapitalizePipe} from './capitalize.pipe'; +import {CapitalizePipe, O3rCapitalizePipe} from './capitalize.pipe'; describe('CapitalizePipe', () => { - const pipe = new CapitalizePipe(); + const pipe = new O3rCapitalizePipe(); + const deprecatedPipe = new CapitalizePipe(); it('transforms "abc" to "Abc"', () => { expect(pipe.transform('abc')).toBe('Abc'); + expect(deprecatedPipe.transform('abc')).toBe('Abc'); }); it('transforms "abc def" to "Abc def"', () => { expect(pipe.transform('abc def')).toBe('Abc def'); + expect(deprecatedPipe.transform('abc def')).toBe('Abc def'); }); it('ignores whitespace', () => { expect(pipe.transform(' ')).toBe(' '); + expect(deprecatedPipe.transform(' ')).toBe(' '); }); it('does not break on empty values', () => { expect(pipe.transform()).toBe(undefined); + expect(deprecatedPipe.transform()).toBe(undefined); expect(pipe.transform(null)).toBe(null); + expect(deprecatedPipe.transform(null)).toBe(null); }); }); diff --git a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.ts b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.ts index dcdd68a180..43bbc5b3cc 100644 --- a/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.ts +++ b/packages/@o3r/components/src/tools/pipes/capitalize/capitalize.pipe.ts @@ -1,10 +1,17 @@ import {Pipe, PipeTransform} from '@angular/core'; -@Pipe({name: 'capitalize'}) -export class CapitalizePipe implements PipeTransform { +@Pipe({name: 'o3rCapitalize', standalone: true}) +export class O3rCapitalizePipe implements PipeTransform { public transform(value?: any) { const val: string | undefined = value && value.toString && value.toString(); const firstLetter: string | undefined = val && val.charAt(0); return firstLetter ? firstLetter.toUpperCase() + val!.slice(1) : value; } } + +/** + * @deprecated please use O3rCapitalizePipe, will be removed in v12. + */ +@Pipe({name: 'capitalize'}) +export class CapitalizePipe extends O3rCapitalizePipe implements PipeTransform {} + diff --git a/packages/@o3r/components/src/tools/pipes/duration/duration.module.ts b/packages/@o3r/components/src/tools/pipes/duration/duration.module.ts index 066c45b9c5..9fba645d13 100644 --- a/packages/@o3r/components/src/tools/pipes/duration/duration.module.ts +++ b/packages/@o3r/components/src/tools/pipes/duration/duration.module.ts @@ -1,6 +1,9 @@ import {NgModule} from '@angular/core'; import {DurationPipe} from './duration.pipe'; +/** + * @deprecated please use O3rDurationPipe, will be removed in v12. + */ @NgModule({ declarations: [DurationPipe], exports: [DurationPipe] diff --git a/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.spec.ts b/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.spec.ts index 22136c39dd..f8f65476c9 100644 --- a/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.spec.ts +++ b/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.spec.ts @@ -1,54 +1,67 @@ -import {DurationPipe} from './duration.pipe'; +import {DurationPipe, O3rDurationPipe} from './duration.pipe'; describe('DurationPipe', () => { - const pipe = new DurationPipe(); + const pipe = new O3rDurationPipe(); + const deprecatedPipe = new DurationPipe(); it('transforms 120s to 0:02', () => { expect(pipe.transform(120)).toBe('0:02'); + expect(deprecatedPipe.transform(120)).toBe('0:02'); }); it('transforms 100s to 0:01', () => { expect(pipe.transform(100)).toBe('0:01'); + expect(deprecatedPipe.transform(100)).toBe('0:01'); }); it('transforms 3660 seconds to "1:01"', () => { expect(pipe.transform(3660)).toBe('1:01'); + expect(deprecatedPipe.transform(3660)).toBe('1:01'); }); it('transforms 90000s to "25:00" (duration exceeds 24 hours)', () => { expect(pipe.transform(90000)).toBe('25:00'); + expect(deprecatedPipe.transform(90000)).toBe('25:00'); }); it('transforms 360000s to "100:00" (hours are 3 digits long)', () => { expect(pipe.transform(360000)).toBe('100:00'); + expect(deprecatedPipe.transform(360000)).toBe('100:00'); }); it('transforms 120s to 0h02m', () => { expect(pipe.transform(120, '{h}h{mm}m')).toBe('0h02m'); + expect(deprecatedPipe.transform(120, '{h}h{mm}m')).toBe('0h02m'); }); it('transforms 120s to 00H02M', () => { expect(pipe.transform(120, '{hh}H{mm}M')).toBe('00H02M'); + expect(deprecatedPipe.transform(120, '{hh}H{mm}M')).toBe('00H02M'); }); it('transforms 3660s to 1h01', () => { expect(pipe.transform(3660, '{h}h{mm}')).toBe('1h01'); + expect(deprecatedPipe.transform(3660, '{h}h{mm}')).toBe('1h01'); }); it('transforms 86399s to 0d23h59m', () => { expect(pipe.transform(86399, '{d}d{h}h{mm}m')).toBe('0d23h59m'); + expect(deprecatedPipe.transform(86399, '{d}d{h}h{mm}m')).toBe('0d23h59m'); }); it('transforms 86399s to 0d86399s', () => { expect(pipe.transform(86399, '{d}d{s}s')).toBe('0d86399s'); + expect(deprecatedPipe.transform(86399, '{d}d{s}s')).toBe('0d86399s'); }); it('transforms 93675s to an object "{"d": 1, "h": 2, "m": 1, "s": 15}"', () => { expect(pipe.transform(93675, '{"d": {d}, "h": {h}, "m": {m}, "s": {s}}')).toBe('{"d": 1, "h": 2, "m": 1, "s": 15}'); + expect(deprecatedPipe.transform(93675, '{"d": {d}, "h": {h}, "m": {m}, "s": {s}}')).toBe('{"d": 1, "h": 2, "m": 1, "s": 15}'); }); it('returns pattern when regex not respected', () => { expect(pipe.transform(1234, 'hh:mm')).toBe('hh:mm'); + expect(deprecatedPipe.transform(1234, 'hh:mm')).toBe('hh:mm'); }); it('should work for custom unit times', () => { @@ -61,6 +74,15 @@ describe('DurationPipe', () => { divider: 25 } ])).toBe('57:1'); + expect(deprecatedPipe.transform(5725, '{t}:{k}', [ + { + formatCharacter: 't', + divider: 100 + }, { + formatCharacter: 'k', + divider: 25 + } + ])).toBe('57:1'); }); it('should work for unit times that are not present in the pattern', () => { @@ -77,5 +99,18 @@ describe('DurationPipe', () => { modulo: 25 } ])).toBe('57:0'); + expect(deprecatedPipe.transform(5725, '{t}:{k}', [ + { + formatCharacter: 't', + divider: 100 + }, { + formatCharacter: 'o', + divider: 25 + }, { + formatCharacter: 'k', + divider: 1, + modulo: 25 + } + ])).toBe('57:0'); }); }); diff --git a/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.ts b/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.ts index 78f6ff7b8b..4a38cb8d72 100644 --- a/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.ts +++ b/packages/@o3r/components/src/tools/pipes/duration/duration.pipe.ts @@ -17,9 +17,8 @@ function padNum(num: number, digits: number) { /** * Converts a duration in seconds into the HH:mm format */ -@Pipe({name: 'duration'}) -export class DurationPipe implements PipeTransform { - +@Pipe({name: 'o3rDuration', standalone: true}) +export class O3rDurationPipe implements PipeTransform { /** * @param value the value in seconds * @param pattern the desired output format. @@ -65,3 +64,10 @@ export class DurationPipe implements PipeTransform { } } + +/** + * Converts a duration in seconds into the HH:mm format + * @deprecated please use O3rDurationPipe, will be removed in v12. + */ +@Pipe({name: 'duration'}) +export class DurationPipe extends O3rDurationPipe implements PipeTransform {} diff --git a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.module.ts b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.module.ts index 0c22dc3d0c..9b1b33becb 100644 --- a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.module.ts +++ b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.module.ts @@ -1,6 +1,9 @@ import {NgModule} from '@angular/core'; import {KeepWhiteSpacePipe} from './keep-white-space.pipe'; +/** + * @deprecated please use O3rKeepWhiteSpacePipe, will be removed in v12. + */ @NgModule({ declarations: [KeepWhiteSpacePipe], exports: [KeepWhiteSpacePipe] diff --git a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.spec.ts b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.spec.ts index 795edb7d57..d85fe33221 100644 --- a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.spec.ts +++ b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.spec.ts @@ -1,31 +1,26 @@ -import {KeepWhiteSpacePipe} from './keep-white-space.pipe'; +import {KeepWhiteSpacePipe, O3rKeepWhiteSpacePipe} from './keep-white-space.pipe'; describe('Keep white space pipe', () => { - let pipe: KeepWhiteSpacePipe; + let pipe: O3rKeepWhiteSpacePipe; + let deprecatedPipe: KeepWhiteSpacePipe; beforeEach(() => { - pipe = new KeepWhiteSpacePipe(); + pipe = new O3rKeepWhiteSpacePipe(); + deprecatedPipe = new KeepWhiteSpacePipe(); }); it('does nothing when no white spaces', () => { - - const transformedString = pipe.transform('Guatemala'); - - expect(transformedString).toEqual('Guatemala'); + expect(pipe.transform('Guatemala')).toEqual('Guatemala'); + expect(deprecatedPipe.transform('Guatemala')).toEqual('Guatemala'); }); it('should work with space inside the word', () => { - - const transformedString = pipe.transform('French Guiana'); - - expect(transformedString).toEqual('French Guiana'); + expect(pipe.transform('French Guiana')).toEqual('French Guiana'); + expect(deprecatedPipe.transform('French Guiana')).toEqual('French Guiana'); }); it('should do nothing when empty string', () => { - - const transformedString = pipe.transform(''); - - expect(transformedString).toEqual(''); + expect(pipe.transform('')).toEqual(''); + expect(deprecatedPipe.transform('')).toEqual(''); }); - }); diff --git a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.ts b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.ts index 13282fea78..cd4fbc5834 100644 --- a/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.ts +++ b/packages/@o3r/components/src/tools/pipes/keep-white-space/keep-white-space.pipe.ts @@ -1,11 +1,18 @@ import {Pipe, PipeTransform} from '@angular/core'; @Pipe({ - name: 'keepWhiteSpace', - pure: true + name: 'o3rKeepWhiteSpace' }) -export class KeepWhiteSpacePipe implements PipeTransform { +export class O3rKeepWhiteSpacePipe implements PipeTransform { public transform(value: string): string { return value.replace(/\s/g, ' '); } } + +/** + * @deprecated please use O3rKeepWhiteSpacePipe, will be removed in v12. + */ +@Pipe({ + name: 'keepWhiteSpace' +}) +export class KeepWhiteSpacePipe extends O3rKeepWhiteSpacePipe implements PipeTransform {} diff --git a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.module.ts b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.module.ts index 3ba559425b..2d5743ad33 100644 --- a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.module.ts +++ b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.module.ts @@ -1,6 +1,9 @@ import {NgModule} from '@angular/core'; import {ReplaceWithBoldPipe} from './replace-with-bold.pipe'; +/** + * @deprecated please use O3rReplaceWithBoldPipe, will be removed in v12. + */ @NgModule({ declarations: [ReplaceWithBoldPipe], exports: [ReplaceWithBoldPipe] diff --git a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.spec.ts b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.spec.ts index 7ee79c7825..7f517a89b2 100644 --- a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.spec.ts +++ b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.spec.ts @@ -1,59 +1,47 @@ -import {ReplaceWithBoldPipe} from './replace-with-bold.pipe'; +import {O3rReplaceWithBoldPipe, ReplaceWithBoldPipe} from './replace-with-bold.pipe'; describe('Replace with bold pipe', () => { - let pipe: ReplaceWithBoldPipe; + let pipe: O3rReplaceWithBoldPipe; + let deprecatedPipe: ReplaceWithBoldPipe; beforeEach(() => { - pipe = new ReplaceWithBoldPipe(); + pipe = new O3rReplaceWithBoldPipe(); + deprecatedPipe = new ReplaceWithBoldPipe(); }); it('search if the input match the label and change it to bold', () => { - - const transformedString = pipe.transform('Guatemala', 'te'); - - expect(transformedString).toEqual('Guatemala'); + expect(pipe.transform('Guatemala', 'te')).toEqual('Guatemala'); + expect(deprecatedPipe.transform('Guatemala', 'te')).toEqual('Guatemala'); }); it('does nothing when no match', () => { - - const transformedString = pipe.transform('Guatemala', 'sp'); - - expect(transformedString).toEqual('Guatemala'); + expect(pipe.transform('Guatemala', 'sp')).toEqual('Guatemala'); + expect(deprecatedPipe.transform('Guatemala', 'sp')).toEqual('Guatemala'); }); it('should work with space inside the word', () => { - - const transformedString = pipe.transform('French Guiana', 'g'); - - expect(transformedString).toEqual('French Guiana'); + expect(pipe.transform('French Guiana', 'g')).toEqual('French Guiana'); + expect(deprecatedPipe.transform('French Guiana', 'g')).toEqual('French Guiana'); }); it('search input match - special character test', () => { - - const transformedString = pipe.transform('Finland (HEL)', '(H'); - - expect(transformedString).toEqual('Finland (HEL)'); + expect(pipe.transform('Finland (HEL)', '(H')).toEqual('Finland (HEL)'); + expect(deprecatedPipe.transform('Finland (HEL)', '(H')).toEqual('Finland (HEL)'); }); it('should ignore white spaces at the beginning', () => { - - const transformedString = pipe.transform('Finland (HEL)', ' (H'); - - expect(transformedString).toEqual('Finland (HEL)'); + expect(pipe.transform('Finland (HEL)', ' (H')).toEqual('Finland (HEL)'); + expect(deprecatedPipe.transform('Finland (HEL)', ' (H')).toEqual('Finland (HEL)'); }); it('should ignore white spaces', () => { - - const transformedString = pipe.transform('Finland (HEL)', ' (H '); - - expect(transformedString).toEqual('Finland (HEL)'); + expect(pipe.transform('Finland (HEL)', ' (H ')).toEqual('Finland (HEL)'); + expect(deprecatedPipe.transform('Finland (HEL)', ' (H ')).toEqual('Finland (HEL)'); }); it('should not ignore white spaces in the middle (no matches)', () => { - - const transformedString = pipe.transform('Finland (HEL)', ' (H E'); - - expect(transformedString).toEqual('Finland (HEL)'); + expect(pipe.transform('Finland (HEL)', ' (H E')).toEqual('Finland (HEL)'); + expect(deprecatedPipe.transform('Finland (HEL)', ' (H E')).toEqual('Finland (HEL)'); }); }); diff --git a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.ts b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.ts index e599b6a679..eb6c385e01 100644 --- a/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.ts +++ b/packages/@o3r/components/src/tools/pipes/replace-with-bold/replace-with-bold.pipe.ts @@ -1,20 +1,30 @@ import {Pipe, PipeTransform} from '@angular/core'; +const escapeRegExp = (str: string) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); + @Pipe({ - name: 'replaceWithBold', - pure: true + name: 'o3rReplaceWithBold', + standalone: true }) -export class ReplaceWithBoldPipe implements PipeTransform { +export class O3rReplaceWithBoldPipe implements PipeTransform { public transform(value: string, inputText: string): string { if (inputText) { - const regexp = new RegExp(this.escapeRegExp(inputText.trim()), 'gi'); + const regexp = new RegExp(escapeRegExp(inputText.trim()), 'gi'); return value.replace(regexp, (match) => `${match}`); } else { return value; } } +} +/** + * @deprecated please use O3rReplaceWithBoldPipe, will be removed in v12. + */ +@Pipe({ + name: 'replaceWithBold' +}) +export class ReplaceWithBoldPipe extends O3rReplaceWithBoldPipe implements PipeTransform { public escapeRegExp(str: string) { - return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); + return escapeRegExp(str); } } diff --git a/packages/@o3r/dynamic-content/README.md b/packages/@o3r/dynamic-content/README.md index 344e0d8085..9edb15e5b0 100644 --- a/packages/@o3r/dynamic-content/README.md +++ b/packages/@o3r/dynamic-content/README.md @@ -37,8 +37,8 @@ The module provides two things: A pipe to be used in your component templates: ```html - or - + or + ``` and a service to be used in your component classes, for example: @@ -151,7 +151,7 @@ It also looks for overrides in the `AssetPathOverrideStore`, so it will return t ## AssetPathOverrideStore -A dedicated store is available in case you want to override any media path. +A dedicated store is available in case you want to override any media path. This store contains a mapping between the current file path and the one that should be used instead. This override ONLY WORKS for media resources. diff --git a/packages/@o3r/dynamic-content/migration.json b/packages/@o3r/dynamic-content/migration.json new file mode 100644 index 0000000000..62836c1d0a --- /dev/null +++ b/packages/@o3r/dynamic-content/migration.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular_devkit/schematics/collection-schema.json", + "schematics": { + "migration-v10_0": { + "version": "10.0.0-alpha.0", + "description": "Updates of the Otter Library to v10.0.*", + "factory": "./schematics/ng-update/v10-0/index#updateV10_0" + } + } +} diff --git a/packages/@o3r/dynamic-content/package.json b/packages/@o3r/dynamic-content/package.json index cabdfb5bce..c0e31e1211 100644 --- a/packages/@o3r/dynamic-content/package.json +++ b/packages/@o3r/dynamic-content/package.json @@ -22,7 +22,7 @@ "build": "yarn nx build dynamic-content", "build:fixtures:jest": "tsc -b tsconfig.fixture.jest.json --pretty", "build:fixtures:jasmine": "tsc -b tsconfig.fixture.jasmine.json --pretty", - "prepare:build:builders": "yarn cpy 'schematics/**/*.json' dist/schematics && yarn cpy 'collection.json' dist && yarn cpy 'schemas/*.json' dist/schemas", + "prepare:build:builders": "yarn cpy 'schematics/**/*.json' dist/schematics && yarn cpy '{collection,migration}.json' dist && yarn cpy 'schemas/*.json' dist/schemas", "prepare:publish": "prepare-publish ./dist" }, "peerDependencies": { diff --git a/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.spec.ts b/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.spec.ts new file mode 100644 index 0000000000..da2a704f9b --- /dev/null +++ b/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.spec.ts @@ -0,0 +1,75 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const migrationPath = path.join(__dirname, '..', '..', '..', 'migration.json'); + + +describe('Update v10', () => { + describe('Update pipes', () => { + let initialTree: Tree; + let runner: SchematicTestRunner; + beforeEach(() => { + initialTree = Tree.empty(); + initialTree.create('angular.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'angular.mocks.json'))); + initialTree.create('package.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'package.mocks.json'))); + initialTree.create('.eslintrc.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', '__dot__eslintrc.mocks.json'))); + initialTree.create('src/components/example.template.html', '{{ "asset.png" | dynamicContent }}'); + runner = new SchematicTestRunner('schematics', migrationPath); + }); + + it('should replace the pipe with standalone component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + import { DynamicContentModule } from '@o3r/dynamic-content'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + standalone: true, + imports: [DynamicContentModule], + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.component.ts')).toMatch('DynamicContentModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rDynamicContent'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| dynamicContent'); + }); + + it('should replace the pipe with module based component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + initialTree.create('src/components/example.module.ts', ` + import { NgModule } from '@angular/core'; + import { DynamicContentModule } from '@o3r/dynamic-content'; + import { ExampleComponent } from './example.component'; + + @NgModule({ + imports: [DynamicContentModule], + declarations: [ExampleComponent], + exports: [ExampleComponent] + }) + export class ExampleModule {} + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.module.ts')).toMatch('DynamicContentModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rDynamicContent'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| dynamicContent'); + }); + }); +}); diff --git a/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.ts b/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.ts new file mode 100644 index 0000000000..2b2d43b18d --- /dev/null +++ b/packages/@o3r/dynamic-content/schematics/ng-update/v10-0/index.ts @@ -0,0 +1,31 @@ +/* eslint-disable camelcase, @typescript-eslint/naming-convention */ +import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { createSchematicWithMetricsIfInstalled, PipeReplacementInfo, updatePipes } from '@o3r/schematics'; + +const pipeReplacementInfo: PipeReplacementInfo = { + dynamicContent: { + new: { + name: 'o3rDynamicContent' + }, + import: 'DynamicContentModule' + } +}; + +/** + * Update of Otter library V10.0 + */ +function updateV10_0Fn(): Rule { + return (tree: Tree, context: SchematicContext) => { + + const updateRules: Rule[] = [ + updatePipes(pipeReplacementInfo) + ]; + + return chain(updateRules)(tree, context); + }; +} + +/** + * Update of Otter library V10.0 + */ +export const updateV10_0 = createSchematicWithMetricsIfInstalled(updateV10_0Fn); diff --git a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.module.ts b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.module.ts index f97eacd55a..d6db2687e0 100644 --- a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.module.ts +++ b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.module.ts @@ -1,5 +1,5 @@ import { ModuleWithProviders, NgModule } from '@angular/core'; -import { DynamicContentPipe } from './dynamic-content.pipe'; +import { DynamicContentPipe, O3rDynamicContentPipe } from './dynamic-content.pipe'; import { DynamicContentService } from './dynamic-content.service'; import { CMS_ASSETS_PATH_TOKEN, DYNAMIC_CONTENT_BASE_PATH_TOKEN } from './dynamic-content.token'; @@ -19,7 +19,7 @@ export function getCmsAssets() { } @NgModule({ - declarations: [DynamicContentPipe], + declarations: [DynamicContentPipe, O3rDynamicContentPipe], providers: [ { provide: DYNAMIC_CONTENT_BASE_PATH_TOKEN, @@ -31,7 +31,7 @@ export function getCmsAssets() { }, DynamicContentService ], - exports: [DynamicContentPipe] + exports: [DynamicContentPipe, O3rDynamicContentPipe] }) /** * DynamicContent module diff --git a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.spec.ts b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.spec.ts index b310204a55..785ba162af 100644 --- a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.spec.ts +++ b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, getTestBed, TestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { InterfaceOf } from '@o3r/core'; import { of } from 'rxjs'; -import { DynamicContentPipe } from './dynamic-content.pipe'; +import { DynamicContentPipe, O3rDynamicContentPipe } from './dynamic-content.pipe'; import { DynamicContentService } from './dynamic-content.service'; @@ -15,7 +15,7 @@ const serviceMock: InterfaceOf = { @Component({ // eslint-disable-next-line @typescript-eslint/quotes - template: `{{'assets.png' | dynamicContent}}` + template: `{{'assets.png' | o3rDynamicContent}}{{'deprecatedPipe.png' | dynamicContent}}` }) class HostTestComponent {} @@ -28,7 +28,7 @@ describe('DynamicContentPipe', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [DynamicContentPipe, HostTestComponent], + declarations: [O3rDynamicContentPipe, DynamicContentPipe, HostTestComponent], providers: [{provide: DynamicContentService, useValue: serviceMock}] }).compileComponents(); @@ -39,6 +39,7 @@ describe('DynamicContentPipe', () => { fixture.detectChanges(); expect(serviceMock.getMediaPathStream).toHaveBeenCalledWith('assets.png'); + expect(serviceMock.getMediaPathStream).toHaveBeenCalledWith('deprecatedPipe.png'); }); }); diff --git a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.ts b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.ts index 0bed59e4a0..89ab7c5881 100644 --- a/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.ts +++ b/packages/@o3r/dynamic-content/src/services/dynamic-content/dynamic-content.pipe.ts @@ -2,18 +2,18 @@ import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core import { Subscription } from 'rxjs'; import { DynamicContentService } from './dynamic-content.service'; -@Pipe({name: 'dynamicContent', pure: false}) -export class DynamicContentPipe implements PipeTransform, OnDestroy { +@Pipe({name: 'o3rDynamicContent', pure: false}) +export class O3rDynamicContentPipe implements PipeTransform, OnDestroy { /** Last query value */ - private lastQuery?: string; + protected lastQuery?: string; /** Subscription to retrieve media path */ - private onMediaPathChange?: Subscription; + protected onMediaPathChange?: Subscription; /** Path to the media */ - private mediaPath = ''; + protected mediaPath = ''; - constructor(private readonly service: DynamicContentService, private readonly cd: ChangeDetectorRef) {} + constructor(protected readonly service: DynamicContentService, protected readonly cd: ChangeDetectorRef) {} /** @inheritDoc */ public transform(query?: string) { @@ -38,3 +38,7 @@ export class DynamicContentPipe implements PipeTransform, OnDestroy { } } } + +/** @deprecated please use O3rDynamicContentPipe, will be removed in v12. */ +@Pipe({name: 'dynamicContent', pure: false}) +export class DynamicContentPipe extends O3rDynamicContentPipe implements PipeTransform {} diff --git a/packages/@o3r/dynamic-content/src/services/request-parameters/request-parameters.spec.ts b/packages/@o3r/dynamic-content/src/services/request-parameters/request-parameters.spec.ts index 748e8b5b11..a5b3aed391 100644 --- a/packages/@o3r/dynamic-content/src/services/request-parameters/request-parameters.spec.ts +++ b/packages/@o3r/dynamic-content/src/services/request-parameters/request-parameters.spec.ts @@ -303,7 +303,7 @@ describe('RequestParametersService', () => { imports: [RequestParametersModule.forRoot(getConfiguration)] }).compileComponents(); - service = TestBed.get(RequestParametersService); + service = TestBed.inject(RequestParametersService); }); afterEach(() => { @@ -505,7 +505,7 @@ describe('RequestParametersService', () => { imports: [RequestParametersModule.forRoot(getConfiguration)] }).compileComponents(); - service = TestBed.get(RequestParametersService); + service = TestBed.inject(RequestParametersService); }); it('should be defined', () => { diff --git a/packages/@o3r/dynamic-content/testing/mocks/__dot__eslintrc.mocks.json b/packages/@o3r/dynamic-content/testing/mocks/__dot__eslintrc.mocks.json new file mode 100644 index 0000000000..85e3a25942 --- /dev/null +++ b/packages/@o3r/dynamic-content/testing/mocks/__dot__eslintrc.mocks.json @@ -0,0 +1,5 @@ +{ + "rules": { + "ban": [true, "fit", "fdescribe"] + } +} diff --git a/packages/@o3r/dynamic-content/testing/mocks/angular.mocks.json b/packages/@o3r/dynamic-content/testing/mocks/angular.mocks.json new file mode 100644 index 0000000000..c34167f9cc --- /dev/null +++ b/packages/@o3r/dynamic-content/testing/mocks/angular.mocks.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular/cli/lib/config/workspace-schema.json", + "version": 1, + "newProjectRoot": ".", + "projects": { + "test-project": { + "projectType": "application", + "root": ".", + "sourceRoot": "./src", + "prefix": "tst", + "architect": { + "build": { + "builder": "", + "options": { + "tsConfig": "tsconfig.json" + } + } + } + } + } +} diff --git a/packages/@o3r/dynamic-content/testing/mocks/package.mocks.json b/packages/@o3r/dynamic-content/testing/mocks/package.mocks.json new file mode 100644 index 0000000000..c64b320ebd --- /dev/null +++ b/packages/@o3r/dynamic-content/testing/mocks/package.mocks.json @@ -0,0 +1,5 @@ +{ + "name": "test-project", + "version": "0.0.0", + "description": "Test project" +} diff --git a/packages/@o3r/dynamic-content/tsconfig.spec.json b/packages/@o3r/dynamic-content/tsconfig.spec.json index 01fea8bf1e..7e4c561a63 100644 --- a/packages/@o3r/dynamic-content/tsconfig.spec.json +++ b/packages/@o3r/dynamic-content/tsconfig.spec.json @@ -6,7 +6,7 @@ "rootDir": ".", }, "include": [ - "./src/**/*.spec.ts" + "./**/*.spec.ts" ], "exclude": [], "references": [ diff --git a/packages/@o3r/localization/README.md b/packages/@o3r/localization/README.md index 89cbda455d..2234212c56 100644 --- a/packages/@o3r/localization/README.md +++ b/packages/@o3r/localization/README.md @@ -201,7 +201,7 @@ export class SimpleHeaderPresModule {} -{{ "o3r-simple-header-pres.motto" | translate }} // => this will output Let's shape the future of travel +{{ "o3r-simple-header-pres.motto" | o3rTranslate }} // => this will output Let's shape the future of travel
// => this will output Hello, otter friend! @@ -214,7 +214,7 @@ export class SimpleHeaderPresModule {} - + ``` @@ -226,7 +226,7 @@ As a result "**hello bold**" will be printed inside the span element. -{{ "someBagsAdded" | translate:{bags: 5} }} // => will output "You have added 5 bags" +{{ "someBagsAdded" | o3rTranslate:{bags: 5} }} // => will output "You have added 5 bags" ``` @@ -479,7 +479,7 @@ import {MESSAGE_FORMAT_CONFIG} from 'ngx-translate-messageformat-compiler'; ```typescript // component html template ... - {{translations.nbOfErrors | translate: {count: countMessages} }} + {{translations.nbOfErrors | o3rTranslate: {count: countMessages} }} ... ``` @@ -504,9 +504,9 @@ Sometimes you may want to display a different resource based on some property va ```typescript // in component html
    -
  • {{ translations.people | translate: { gender: 'female', how: 'influential' } }}
  • -
  • {{ translations.people | translate: { gender: 'male', how: 'funny' } }}
  • -
  • {{ translations.people | translate: { how: 'affectionate' } }}
  • +
  • {{ translations.people | o3rTranslate: { gender: 'female', how: 'influential' } }}
  • +
  • {{ translations.people | o3rTranslate: { gender: 'male', how: 'funny' } }}
  • +
  • {{ translations.people | o3rTranslate: { how: 'affectionate' } }}
``` diff --git a/packages/@o3r/localization/migration.json b/packages/@o3r/localization/migration.json new file mode 100644 index 0000000000..62836c1d0a --- /dev/null +++ b/packages/@o3r/localization/migration.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular_devkit/schematics/collection-schema.json", + "schematics": { + "migration-v10_0": { + "version": "10.0.0-alpha.0", + "description": "Updates of the Otter Library to v10.0.*", + "factory": "./schematics/ng-update/v10-0/index#updateV10_0" + } + } +} diff --git a/packages/@o3r/localization/package.json b/packages/@o3r/localization/package.json index 4ae3a04fa3..00d280697e 100644 --- a/packages/@o3r/localization/package.json +++ b/packages/@o3r/localization/package.json @@ -18,7 +18,7 @@ "ng": "yarn nx", "copy:templates": "yarn cpy 'schematics/**/templates/**' dist/schematics", "copy:schemas": "yarn cpy 'schemas/*.json' dist/schemas", - "prepare:build:builders": "yarn cpy 'builders/**/*.json' dist/builders && yarn cpy '{builders,collection}.json' dist && yarn cpy 'schematics/**/*.json' dist/schematics && yarn copy:templates && yarn copy:schemas", + "prepare:build:builders": "yarn cpy 'builders/**/*.json' dist/builders && yarn cpy '{builders,collection,migration}.json' dist && yarn cpy 'schematics/**/*.json' dist/schematics && yarn copy:templates && yarn copy:schemas", "prepare:compile": "cp-package-json", "prepare:publish": "prepare-publish ./dist", "build:builders": "tsc -b tsconfig.builders.json --pretty && yarn generate-cjs-manifest", diff --git a/packages/@o3r/localization/schematics/add-localization-key/index.spec.ts b/packages/@o3r/localization/schematics/add-localization-key/index.spec.ts index 4fdaa0910c..4c2ec8d69b 100644 --- a/packages/@o3r/localization/schematics/add-localization-key/index.spec.ts +++ b/packages/@o3r/localization/schematics/add-localization-key/index.spec.ts @@ -97,7 +97,7 @@ describe('Add Localization', () => { }, initialTree); const templateFileContent = tree.readText(templatePath); - expect(templateFileContent).toBe('
{{ translations.dummyLoc1 | translate }}
'); + expect(templateFileContent).toBe('
{{ translations.dummyLoc1 | o3rTranslate }}
'); const translationFileContent = tree.readText(translationPath); expect(translationFileContent).toContain('dummyLoc1: string;'); diff --git a/packages/@o3r/localization/schematics/add-localization-key/index.ts b/packages/@o3r/localization/schematics/add-localization-key/index.ts index 428775f164..8ac68fd747 100644 --- a/packages/@o3r/localization/schematics/add-localization-key/index.ts +++ b/packages/@o3r/localization/schematics/add-localization-key/index.ts @@ -227,7 +227,7 @@ export function ngAddLocalizationKeyFn(options: NgAddLocalizationKeySchematicsSc if (templatePath) { tree.overwrite( templatePath, - tree.readText(templatePath).replaceAll(options.value, `{{ translations.${properties.keyName} | translate }}`) + tree.readText(templatePath).replaceAll(options.value, `{{ translations.${properties.keyName} | o3rTranslate }}`) ); } }; diff --git a/packages/@o3r/localization/schematics/localization-to-component/index.spec.ts b/packages/@o3r/localization/schematics/localization-to-component/index.spec.ts index ba4f56fd45..dc4bda6d4e 100644 --- a/packages/@o3r/localization/schematics/localization-to-component/index.spec.ts +++ b/packages/@o3r/localization/schematics/localization-to-component/index.spec.ts @@ -103,7 +103,7 @@ describe('Add Localization', () => { expect(componentFileContent).toContain('@Localization(\'./test.localization.json\')'); const templateFileContent = tree.readText(templatePath); - expect(templateFileContent).toContain('
Localization: {{ translations.dummyLoc1 | translate }}
'); + expect(templateFileContent).toContain('
Localization: {{ translations.dummyLoc1 | o3rTranslate }}
'); const specFileContent = tree.readText(specPath); expect(specFileContent).toContain('const localizationService = TestBed.inject(LocalizationService);'); diff --git a/packages/@o3r/localization/schematics/localization-to-component/index.ts b/packages/@o3r/localization/schematics/localization-to-component/index.ts index f13d155057..ef349a8921 100644 --- a/packages/@o3r/localization/schematics/localization-to-component/index.ts +++ b/packages/@o3r/localization/schematics/localization-to-component/index.ts @@ -227,7 +227,7 @@ export function ngAddLocalizationFn(options: NgAddLocalizationSchematicsSchema): tree.commitUpdate( tree .beginUpdate(templatePath) - .insertLeft(0, '
Localization: {{ translations.dummyLoc1 | translate }}
\n') + .insertLeft(0, '
Localization: {{ translations.dummyLoc1 | o3rTranslate }}
\n') ); } diff --git a/packages/@o3r/localization/schematics/ng-update/v10-0/index.spec.ts b/packages/@o3r/localization/schematics/ng-update/v10-0/index.spec.ts new file mode 100644 index 0000000000..72168d42c1 --- /dev/null +++ b/packages/@o3r/localization/schematics/ng-update/v10-0/index.spec.ts @@ -0,0 +1,75 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const migrationPath = path.join(__dirname, '..', '..', '..', 'migration.json'); + + +describe('Update v10', () => { + describe('Update pipes', () => { + let initialTree: Tree; + let runner: SchematicTestRunner; + beforeEach(() => { + initialTree = Tree.empty(); + initialTree.create('angular.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'angular.mocks.json'))); + initialTree.create('package.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', 'package.mocks.json'))); + initialTree.create('.eslintrc.json', fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'testing', 'mocks', '__dot__eslintrc.mocks.json'))); + initialTree.create('src/components/example.template.html', '{{ "localization.key" | translate }}'); + runner = new SchematicTestRunner('schematics', migrationPath); + }); + + it('should replace the pipe with standalone component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + import { LocalizationModule } from '@o3r/localization'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + standalone: true, + imports: [LocalizationModule], + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.component.ts')).toMatch('LocalizationModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rTranslate'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| translate'); + }); + + it('should replace the pipe with module based component', async () => { + initialTree.create('src/components/example.component.ts', ` + import { Component } from '@angular/core'; + import { O3rComponent } from '@o3r/core'; + + @O3rComponent({ componentType: 'Component' }) + @Component({ + selector: 'o3r-example', + templateUrl: './example.template.html' + }) + export class ExampleComponent { + } + `); + initialTree.create('src/components/example.module.ts', ` + import { NgModule } from '@angular/core'; + import { LocalizationModule } from '@o3r/localization'; + import { ExampleComponent } from './example.component'; + + @NgModule({ + imports: [LocalizationModule], + declarations: [ExampleComponent], + exports: [ExampleComponent] + }) + export class ExampleModule {} + `); + const tree = await runner.runSchematic('migration-v10_0', {}, initialTree); + expect(tree.readText('src/components/example.module.ts')).toMatch('LocalizationModule'); + expect(tree.readText('src/components/example.template.html')).toMatch('| o3rTranslate'); + expect(tree.readText('src/components/example.template.html')).not.toMatch('| translate'); + }); + }); +}); diff --git a/packages/@o3r/localization/schematics/ng-update/v10-0/index.ts b/packages/@o3r/localization/schematics/ng-update/v10-0/index.ts new file mode 100644 index 0000000000..87a0cc1721 --- /dev/null +++ b/packages/@o3r/localization/schematics/ng-update/v10-0/index.ts @@ -0,0 +1,31 @@ +/* eslint-disable camelcase, @typescript-eslint/naming-convention */ +import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { createSchematicWithMetricsIfInstalled, PipeReplacementInfo, updatePipes } from '@o3r/schematics'; + +const pipeReplacementInfo: PipeReplacementInfo = { + translate: { + new: { + name: 'o3rTranslate' + }, + import: 'LocalizationModule' + } +}; + +/** + * Update of Otter library V10.0 + */ +function updateV10_0Fn(): Rule { + return (tree: Tree, context: SchematicContext) => { + + const updateRules: Rule[] = [ + updatePipes(pipeReplacementInfo) + ]; + + return chain(updateRules)(tree, context); + }; +} + +/** + * Update of Otter library V10.0 + */ +export const updateV10_0 = createSchematicWithMetricsIfInstalled(updateV10_0Fn); diff --git a/packages/@o3r/localization/src/tools/localization-translate.pipe.spec.ts b/packages/@o3r/localization/src/tools/localization-translate.pipe.spec.ts index 3e84e9a3d4..1acfdeb79f 100644 --- a/packages/@o3r/localization/src/tools/localization-translate.pipe.spec.ts +++ b/packages/@o3r/localization/src/tools/localization-translate.pipe.spec.ts @@ -3,7 +3,7 @@ import { getTestBed, TestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; -import { LocalizationTranslatePipe } from './localization-translate.pipe'; +import { LocalizationTranslatePipe, O3rLocalizationTranslatePipe } from './localization-translate.pipe'; import { createLocalizationConfiguration, LocalizationModule } from './localization.module'; import { LocalizationService } from './localization.service'; import { LOCALIZATION_CONFIGURATION_TOKEN } from './localization.token'; @@ -36,7 +36,8 @@ describe('LocalizationTranslatePipe', () => { let localizationService: LocalizationService; let translate: TranslateService; - let pipe: LocalizationTranslatePipe; + let pipe: O3rLocalizationTranslatePipe; + let deprecatedPipe: LocalizationTranslatePipe; let ref: any; describe('enableTranslationDeactivation OFF', () => { @@ -51,12 +52,13 @@ describe('LocalizationTranslatePipe', () => { providers: [LocalizationService] }).compileComponents(); - localizationService = TestBed.get(LocalizationService); + localizationService = TestBed.inject(LocalizationService); // initialize TranslateService via configure of LocalizationService localizationService.configure(); - translate = TestBed.get(TranslateService); + translate = TestBed.inject(TranslateService); ref = new FakeChangeDetectorRef(); - pipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.get(LOCALIZATION_CONFIGURATION_TOKEN)); + pipe = new O3rLocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); + deprecatedPipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); expect(() => localizationService.toggleShowKeys()).toThrow(); }); @@ -80,25 +82,30 @@ describe('LocalizationTranslatePipe', () => { }] }).compileComponents(); - localizationService = TestBed.get(LocalizationService); + localizationService = TestBed.inject(LocalizationService); // initialize TranslateService via configure of LocalizationService localizationService.configure(); - translate = TestBed.get(TranslateService); + translate = TestBed.inject(TranslateService); ref = new FakeChangeDetectorRef(); - pipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.get(LOCALIZATION_CONFIGURATION_TOKEN)); + pipe = new O3rLocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); + deprecatedPipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); }); it('Should not translate if ShowKeys is activated', () => { localizationService.toggleShowKeys(); expect(pipe.transform('test')).toEqual('test'); + expect(deprecatedPipe.transform('test')).toEqual('test'); expect(pipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams'); + expect(deprecatedPipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams'); }); it('Should translate if ShowKeys is no activated', () => { expect(pipe.transform('test')).toEqual('This is a test'); + expect(deprecatedPipe.transform('test')).toEqual('This is a test'); expect(pipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('This is a test with param-1 and param-2'); + expect(deprecatedPipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('This is a test with param-1 and param-2'); }); }); @@ -122,27 +129,31 @@ describe('LocalizationTranslatePipe', () => { } ] }).compileComponents(); - localizationService = TestBed.get(LocalizationService); + localizationService = TestBed.inject(LocalizationService); // initialize TranslateService via configure of LocalizationService localizationService.configure(); - translate = TestBed.get(TranslateService); + translate = TestBed.inject(TranslateService); ref = new FakeChangeDetectorRef(); - pipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.get(LOCALIZATION_CONFIGURATION_TOKEN)); + pipe = new O3rLocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); + deprecatedPipe = new LocalizationTranslatePipe(localizationService, translate, ref, TestBed.inject(LOCALIZATION_CONFIGURATION_TOKEN)); }); it('Should not translate if ShowKeys is activated', () => { localizationService.toggleShowKeys(); expect(pipe.transform('test')).toEqual('test'); + expect(deprecatedPipe.transform('test')).toEqual('test'); expect(pipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams'); + expect(deprecatedPipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams'); }); it('Should display both key and value if ShowKeys is no activated', () => { expect(pipe.transform('test')).toEqual('test - This is a test'); + expect(deprecatedPipe.transform('test')).toEqual('test - This is a test'); expect(pipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams - This is a test with param-1 and param-2'); + expect(deprecatedPipe.transform('testParams', '{param1: "with param-1", param2: "and param-2"}')).toEqual('testParams - This is a test with param-1 and param-2'); }); }); }); - }); diff --git a/packages/@o3r/localization/src/tools/localization-translate.pipe.ts b/packages/@o3r/localization/src/tools/localization-translate.pipe.ts index 9e5ff770fb..a33a524c88 100644 --- a/packages/@o3r/localization/src/tools/localization-translate.pipe.ts +++ b/packages/@o3r/localization/src/tools/localization-translate.pipe.ts @@ -4,34 +4,35 @@ import { Subscription } from 'rxjs'; import { LocalizationConfiguration } from '../core'; import { LocalizationService } from './localization.service'; import { LOCALIZATION_CONFIGURATION_TOKEN } from './localization.token'; + /** * TranslatePipe class adding debug functionality */ -@Pipe({name: 'translate', pure: false}) -export class LocalizationTranslatePipe extends TranslatePipe implements PipeTransform { +@Pipe({name: 'o3rTranslate', pure: false}) +export class O3rLocalizationTranslatePipe extends TranslatePipe implements PipeTransform { /** * Internal subscription to the LocalizationService showKeys mode changes */ - private readonly onShowKeysChange?: Subscription; + protected readonly onShowKeysChange?: Subscription; /** * Internal subscription to the LocalizationService key mapping */ - private onKeyChange?: Subscription; + protected onKeyChange?: Subscription; /** * Should we display keys instead of translations */ - private showKeys = false; + protected showKeys = false; /** last key queried */ - private lastQueryKey?: string; + protected lastQueryKey?: string; /** last key resolved */ - private lastResolvedKey?: string; + protected lastResolvedKey?: string; - constructor(private readonly localizationService: LocalizationService, translateService: TranslateService, private readonly changeDetector: ChangeDetectorRef, - @Inject(LOCALIZATION_CONFIGURATION_TOKEN) private readonly localizationConfig: LocalizationConfiguration) { + constructor(protected readonly localizationService: LocalizationService, translateService: TranslateService, protected readonly changeDetector: ChangeDetectorRef, + @Inject(LOCALIZATION_CONFIGURATION_TOKEN) protected readonly localizationConfig: LocalizationConfiguration) { super(translateService, changeDetector); if (localizationConfig.enableTranslationDeactivation) { @@ -83,3 +84,10 @@ export class LocalizationTranslatePipe extends TranslatePipe implements PipeTran } } } + +/** + * TranslatePipe class adding debug functionality + * @deprecated please use O3rLocalizationTranslatePipe, will be removed in v12. + */ +@Pipe({name: 'translate', pure: false}) +export class LocalizationTranslatePipe extends O3rLocalizationTranslatePipe implements PipeTransform {} diff --git a/packages/@o3r/localization/src/tools/localization.module.ts b/packages/@o3r/localization/src/tools/localization.module.ts index 811ac5c173..5edaf230b4 100644 --- a/packages/@o3r/localization/src/tools/localization.module.ts +++ b/packages/@o3r/localization/src/tools/localization.module.ts @@ -5,7 +5,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { DynamicContentModule } from '@o3r/dynamic-content'; import { DEFAULT_LOCALIZATION_CONFIGURATION, LocalizationConfiguration } from '../core'; import { LocalizationTranslateDirective } from './localization-translate.directive'; -import { LocalizationTranslatePipe } from './localization-translate.pipe'; +import { LocalizationTranslatePipe, O3rLocalizationTranslatePipe } from './localization-translate.pipe'; import { LocalizationService } from './localization.service'; import { LOCALIZATION_CONFIGURATION_TOKEN } from './localization.token'; import { LocalizedCurrencyPipe } from './localized-currency.pipe'; @@ -37,9 +37,9 @@ export function localeIdNgBridge(localizationService: LocalizationService) { export const CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN = new InjectionToken>('Partial Localization configuration'); @NgModule({ - declarations: [LocalizationTranslatePipe, LocalizationTranslateDirective, LocalizedDatePipe, LocalizedDecimalPipe, LocalizedCurrencyPipe], + declarations: [O3rLocalizationTranslatePipe, LocalizationTranslatePipe, LocalizationTranslateDirective, LocalizedDatePipe, LocalizedDecimalPipe, LocalizedCurrencyPipe], imports: [TranslateModule, BidiModule, DynamicContentModule, CommonModule], - exports: [TranslateModule, LocalizationTranslatePipe, LocalizationTranslateDirective, LocalizedDatePipe, LocalizedDecimalPipe, LocalizedCurrencyPipe], + exports: [TranslateModule, O3rLocalizationTranslatePipe, LocalizationTranslatePipe, LocalizationTranslateDirective, LocalizedDatePipe, LocalizedDecimalPipe, LocalizedCurrencyPipe], providers: [ {provide: LOCALIZATION_CONFIGURATION_TOKEN, useFactory: createLocalizationConfiguration, deps: [[new Optional(), CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN]]}, {provide: LOCALE_ID, useFactory: localeIdNgBridge, deps: [LocalizationService]}, diff --git a/packages/@o3r/localization/testing/mocks/angular.mocks.json b/packages/@o3r/localization/testing/mocks/angular.mocks.json new file mode 100644 index 0000000000..c34167f9cc --- /dev/null +++ b/packages/@o3r/localization/testing/mocks/angular.mocks.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular/cli/lib/config/workspace-schema.json", + "version": 1, + "newProjectRoot": ".", + "projects": { + "test-project": { + "projectType": "application", + "root": ".", + "sourceRoot": "./src", + "prefix": "tst", + "architect": { + "build": { + "builder": "", + "options": { + "tsConfig": "tsconfig.json" + } + } + } + } + } +} diff --git a/packages/@o3r/localization/testing/mocks/package.mocks.json b/packages/@o3r/localization/testing/mocks/package.mocks.json new file mode 100644 index 0000000000..c64b320ebd --- /dev/null +++ b/packages/@o3r/localization/testing/mocks/package.mocks.json @@ -0,0 +1,5 @@ +{ + "name": "test-project", + "version": "0.0.0", + "description": "Test project" +} diff --git a/packages/@o3r/rules-engine/src/components/rules-engine/rule-actions/rule-actions-pres.template.html b/packages/@o3r/rules-engine/src/components/rules-engine/rule-actions/rule-actions-pres.template.html index ec0bedb6dd..6bf6c1f9dd 100644 --- a/packages/@o3r/rules-engine/src/components/rules-engine/rule-actions/rule-actions-pres.template.html +++ b/packages/@o3r/rules-engine/src/components/rules-engine/rule-actions/rule-actions-pres.template.html @@ -10,8 +10,8 @@
Set Fact
@@ -19,8 +19,8 @@
Update Config {{action.component}} {{action.library}}
@@ -28,8 +28,8 @@
Update Asset:
@@ -37,8 +37,8 @@
Update localization:
@@ -48,7 +48,7 @@
Update placeholder in {{action.component}} {{action.library}}
@@ -64,8 +64,8 @@
Set temporary fact
diff --git a/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.module.ts b/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.module.ts index 5bcd7d6d87..be32fdb078 100644 --- a/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.module.ts +++ b/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.module.ts @@ -4,12 +4,12 @@ import { RuleActionsPresComponent } from '../rule-actions/rule-actions-pres.comp import { RuleConditionPresComponent } from '../rule-condition/rule-condition-pres.component'; import { RuleKeyValuePresComponent } from '../rule-key-value/rule-key-value-pres.component'; import { RuleTreePresComponent } from '../rule-tree/rule-tree-pres.component'; -import { FallbackToPipe } from '../shared/index'; +import { O3rFallbackToPipe } from '../shared/index'; import { RulesetHistoryPresComponent } from './ruleset-history-pres.component'; @NgModule({ - imports: [CommonModule, CommonModule, CommonModule], - declarations: [FallbackToPipe, RulesetHistoryPresComponent, RuleConditionPresComponent, RuleTreePresComponent, RuleActionsPresComponent, RuleKeyValuePresComponent], + imports: [O3rFallbackToPipe, CommonModule, CommonModule, CommonModule], + declarations: [RulesetHistoryPresComponent, RuleConditionPresComponent, RuleTreePresComponent, RuleActionsPresComponent, RuleKeyValuePresComponent], exports: [RulesetHistoryPresComponent] }) export class RulesetHistoryPresModule {} diff --git a/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.template.html b/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.template.html index 327793696d..58061befe0 100644 --- a/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.template.html +++ b/packages/@o3r/rules-engine/src/components/rules-engine/ruleset-history/ruleset-history-pres.template.html @@ -106,8 +106,8 @@

Ruleset Execution History

  • @@ -145,7 +145,7 @@

    Ruleset Execution History

  • {{input}} (scope limited to ruleset)
  • diff --git a/packages/@o3r/rules-engine/src/components/rules-engine/shared/fallback-to.pipe.ts b/packages/@o3r/rules-engine/src/components/rules-engine/shared/fallback-to.pipe.ts index 2cbeb8cecd..c75a4e5fba 100644 --- a/packages/@o3r/rules-engine/src/components/rules-engine/shared/fallback-to.pipe.ts +++ b/packages/@o3r/rules-engine/src/components/rules-engine/shared/fallback-to.pipe.ts @@ -1,8 +1,14 @@ import {Pipe, PipeTransform} from '@angular/core'; -@Pipe({name: 'fallbackTo'}) -export class FallbackToPipe implements PipeTransform { +@Pipe({name: 'o3rFallbackTo', standalone: true}) +export class O3rFallbackToPipe implements PipeTransform { public transform(value: T, fallback = 'undefined'): T | string { return value !== undefined ? value : fallback; } } + +/** + * @deprecated please use O3rFallbackToPipe, will be removed in v12. + */ +@Pipe({name: 'fallbackTo'}) +export class FallbackToPipe extends O3rFallbackToPipe implements PipeTransform {} diff --git a/packages/@o3r/schematics/src/utility/index.ts b/packages/@o3r/schematics/src/utility/index.ts index 02c66797f7..96227516aa 100644 --- a/packages/@o3r/schematics/src/utility/index.ts +++ b/packages/@o3r/schematics/src/utility/index.ts @@ -19,5 +19,6 @@ export * from './routes'; export * from './sub-entry'; export * from './template-property.helper'; export * from './update-imports'; +export * from './update-pipes'; export * from './builder'; export * from './wrapper'; diff --git a/packages/@o3r/schematics/src/utility/update-pipes.ts b/packages/@o3r/schematics/src/utility/update-pipes.ts new file mode 100644 index 0000000000..4728145842 --- /dev/null +++ b/packages/@o3r/schematics/src/utility/update-pipes.ts @@ -0,0 +1,116 @@ +import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { basename, dirname, join } from 'node:path'; +import * as ts from 'typescript'; +import { DecoratorWithArg, isDecoratorWithArg } from './ast'; +import { getO3rComponentInfoOrThrowIfNotFound, isNgClassComponent, isNgClassDecorator } from './component'; +import { findFilesInTree } from './loaders'; + +/** Dictionary of pipes to be updated */ +export type PipeReplacementInfo = Record; + +const applyChanges = ( + pipeReplacementInfo: PipeReplacementInfo, + ngDecorator: DecoratorWithArg, + matchers: RegExpMatchArray[], + fileWithImports: string, + templateFile: string, + tree: Tree, + context: SchematicContext +) => { + if (ts.isObjectLiteralExpression(ngDecorator.expression.arguments[0])) { + const importsProp = ngDecorator.expression.arguments[0].properties.find((prop): prop is ts.PropertyAssignment & { initializer: ts.ArrayLiteralExpression } => + ts.isPropertyAssignment(prop) + && ts.isIdentifier(prop.name) + && prop.name.text === 'imports' + && ts.isArrayLiteralExpression(prop.initializer) + ); + if (importsProp) { + matchers.forEach((matcher) => { + const pipeName = matcher[1]; + const importModuleRegexp = new RegExp(`\\b${pipeReplacementInfo[pipeName].import}\\b`, 'g'); + if (importsProp.initializer.elements.some((element) => importModuleRegexp.test(element.getText()))) { + if (pipeReplacementInfo[pipeName].new.import) { + tree.overwrite( + fileWithImports, + tree.readText(fileWithImports).replaceAll(importModuleRegexp, pipeReplacementInfo[pipeName].new.import!) + ); + } + tree.overwrite( + templateFile, + tree.readText(templateFile).replaceAll(new RegExp(`\\|\\s*${pipeName}`, 'g'), `| ${pipeReplacementInfo[pipeName].new.name}`) + ); + } else { + context.logger.warn( + `Deprecated pipe ${pipeName} was found in ${templateFile} ` + + `but no associated import in ${fileWithImports}` + + `to be able to migrate to the new pipe ${pipeReplacementInfo[pipeName].new.name}.` + ); + } + }); + } + } +}; + +const isNgModuleDecorator = (decorator: ts.Decorator) => + ts.isCallExpression(decorator.expression) + && decorator.expression.expression + && ts.isIdentifier(decorator.expression.expression) + && decorator.expression.expression.text === 'NgModule'; + +/** + * Returns a rule tu update pipes + * @param pipeReplacementInfo + */ +export const updatePipes = (pipeReplacementInfo: PipeReplacementInfo): Rule => (tree, context) => { + const pipeRegex = new RegExp(`\\|\\s*(${Object.keys(pipeReplacementInfo).join('|')})`, 'g'); + const files = findFilesInTree(tree.root, (file) => file.endsWith('.html')); + files.forEach((file) => { + const matchers = Array.from( + file.content.toString().matchAll(pipeRegex) + ).filter((matcher) => !!pipeReplacementInfo[matcher[1]]); + if (matchers.length) { + const directory = dirname(file.path); + const baseFileName = basename(file.path, '.template.html'); + const componentFile = join(directory, `${baseFileName}.component.ts`); + const moduleFile = join(directory, `${baseFileName}.module.ts`); + let standalone = false; + if (tree.exists(componentFile)) { + try { + const info = getO3rComponentInfoOrThrowIfNotFound(tree, componentFile); + standalone = info.standalone; + } catch {} + } + if (standalone) { + const componentSourceFile = ts.createSourceFile( + componentFile, + tree.readText(componentFile), + ts.ScriptTarget.ES2020, + true + ); + const [ngClass] = componentSourceFile.statements.filter((statement): statement is ts.ClassDeclaration => + ts.isClassDeclaration(statement) + && isNgClassComponent(statement) + ); + const [ngDecorator] = ((ngClass && ts.getDecorators(ngClass)) || []).filter(isNgClassDecorator); + if (ngDecorator) { + applyChanges(pipeReplacementInfo, ngDecorator, matchers, componentFile, file.path, tree, context); + } + } else if (tree.exists(moduleFile)) { + const moduleSourceFile = ts.createSourceFile( + moduleFile, + tree.readText(moduleFile), + ts.ScriptTarget.ES2020, + true + ); + const ngModuleClass = moduleSourceFile.statements.find((statement): statement is ts.ClassDeclaration => + ts.isClassDeclaration(statement) + && !!(ts.getDecorators(statement) || []).find(isNgModuleDecorator) + ); + const ngDecorator = ((ngModuleClass && ts.getDecorators(ngModuleClass)) || []).find(isNgModuleDecorator); + if (ngDecorator && isDecoratorWithArg(ngDecorator)) { + applyChanges(pipeReplacementInfo, ngDecorator, matchers, moduleFile, file.path, tree, context); + } + } + } + }); +}; diff --git a/packages/@o3r/testing/src/localization/localization-mock.ts b/packages/@o3r/testing/src/localization/localization-mock.ts index 65aab26d9f..bfd5fbecfe 100644 --- a/packages/@o3r/testing/src/localization/localization-mock.ts +++ b/packages/@o3r/testing/src/localization/localization-mock.ts @@ -17,15 +17,22 @@ export class TranslatePipeMock implements PipeTransform { } } +@Pipe({ name: 'o3rTranslate' }) +export class O3rTranslatePipeMock implements PipeTransform { + public transform(...args: any[]): string | undefined { + return args && args.map((arg) => JSON.parse(arg)).join(', '); + } +} + @NgModule({ - declarations: [TranslatePipeMock], - exports: [TranslatePipeMock] + declarations: [TranslatePipeMock, O3rTranslatePipeMock], + exports: [TranslatePipeMock, O3rTranslatePipeMock] }) export class LocalizationDependencyMocks { - public static forTest(): ModuleWithProviders { + public static forTest(pipeWithPrefix = false): ModuleWithProviders { return { ngModule: LocalizationDependencyMocks, - providers: [{ provide: LocalizationTranslatePipe, useClass: TranslatePipeMock }] + providers: [{ provide: LocalizationTranslatePipe, useClass: pipeWithPrefix ? O3rTranslatePipeMock : TranslatePipeMock }] }; } } @@ -49,7 +56,8 @@ export function mockTranslationModules( localizationConfiguration: Partial = defaultLocalizationConfiguration, translations: MockTranslations = {}, translationCompilerProvider?: Provider, - mockPipe = false + mockPipe = false, + pipeWithPrefix = false ): ModuleWithProviders[] { return [ LocalizationModule.forRoot(() => localizationConfiguration), @@ -62,6 +70,6 @@ export function mockTranslationModules( }, compiler: translationCompilerProvider }), - ...(mockPipe ? [LocalizationDependencyMocks.forTest()] : []) + ...(mockPipe ? [LocalizationDependencyMocks.forTest(pipeWithPrefix)] : []) ]; }