diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b8b4a3c79e..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,15 +0,0 @@ -**/node_modules/** -.cache/** -.yarn/** -dist/ -test/ -tmp/ -generated-doc/ -/{packages,apps}/@*/*/dist*/** -/packages/@*/*/**/templates/** -/apps/*/**/templates/** -/.pnp.js -/.vscode -/tools/github-actions/*/packaged-action/** -/**/src/**/package.json -!/.yarnrc.yml diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e44635f50d..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,126 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable quote-props */ - -module.exports = { - 'parserOptions': { - 'tsconfigRootDir': __dirname, - 'project': [ - 'tsconfig.eslint.json' - ], - 'ecmaVersion': 12 - }, - 'overrides': [ - { - 'files': [ - '*.{c,m,}{t,j}s' - ], - 'parser': require.resolve('@typescript-eslint/parser'), - 'extends': ['@o3r/eslint-config-otter'].map(require.resolve) - }, - { - 'files': [ - '*{.,-}jasmine.ts' - ], - 'rules': { - 'jest/no-jasmine-globals': 'off' - } - }, - - { - 'files': [ - '*.spec.ts' - ], - 'rules': { - '@typescript-eslint/no-require-imports': 'off' // required by Jest to mock the imports - } - }, - - { - 'files': [ - '*{.,-}jasmine.ts' - ], - 'rules': { - 'jest/no-jasmine-globals': 'off' - } - }, - - { - 'parser': require.resolve('jsonc-eslint-parser'), - 'files': [ - '**/*.json' - ] - }, - { - 'files': [ - '**/package.json' - ], - 'plugins': [ - '@nx', - '@o3r' - ], - 'rules': { - '@o3r/json-dependency-versions-harmonize': ['error', { - ignoredPackages: ['@o3r/build-helpers', '@o3r/workspace-helpers'], - alignPeerDependencies: false, - alignEngines: true - }], - '@nx/dependency-checks': ['error', { - 'buildTargets': ['build', 'build-builders', 'compile', 'test'], - 'checkObsoleteDependencies': false, - 'checkVersionMismatches': false, - 'ignoredDependencies': ['ora', '@o3r/test-helpers'] - }] - } - }, - { - 'files': [ - './package.json' - ], - 'plugins': [ - '@o3r' - ], - 'rules': { - '@o3r/json-dependency-versions-harmonize': ['error', { - ignoredPackages: ['@o3r/build-helpers', '@o3r/workspace-helpers'], - ignoredDependencies: ['npm'], - alignPeerDependencies: false, - alignEngines: true - }] - } - }, - - { - 'parser': require.resolve('yaml-eslint-parser'), - 'files': [ - '**/*.y{a,}ml' - ] - }, - { - 'files': [ - '**/.yarnrc.yml' - ], - 'plugins': [ - '@o3r' - ], - 'rules': { - '@o3r/yarnrc-package-extensions-harmonize': ['error'] - } - } - ], - 'env': { - 'es2021': true, - 'browser': true, - 'node': true, - 'webextensions': true, - - 'jasmine': true, - 'jest': true, - 'jest/globals': true - }, - 'globals': { - 'globalThis': true - }, - 'settings': { - 'import/resolver': 'node' - } -}; diff --git a/.vscode/settings.json b/.vscode/settings.json index f2561bf074..c8ac246f0b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,7 @@ "package.json": "ng-package.json, project.json, yarn.lock, .yarnrc.yml, .npmrc, .npmrc.*, .pnp.*", "nx.json": ".nxignore", ".eslintrc.*": ".eslintignore, .eslintrc-*", + "eslint.config.mjs": "eslint.*.config.mjs", "jest.config.js": "jest.config.*", "Dockerfile*": ".dockerignore" }, @@ -97,5 +98,23 @@ "files.trimTrailingWhitespace": true, "files.trimFinalNewlines": false, "otter.extract.styling.prefix": "o3r", - "typescript.tsserver.watchOptions": "vscode" + "typescript.tsserver.watchOptions": "vscode", + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.foldingImportsByDefault": true, + "eslint.useFlatConfig": true, + "eslint.rules.customizations": [ + { + "rule": "@stylistic/*", + "fixable": true, + "severity": "off" + }, + { + "rule": "!@stylistic/*", + "fixable": true, + "severity": "info" + } + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } } diff --git a/.yarn/sdks/eslint/lib/types/index.d.ts b/.yarn/sdks/eslint/lib/types/index.d.ts new file mode 100644 index 0000000000..19293d02e4 --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/index.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint`)); diff --git a/.yarn/sdks/eslint/lib/types/rules/index.d.ts b/.yarn/sdks/eslint/lib/types/rules/index.d.ts new file mode 100644 index 0000000000..a4ae666c6d --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/rules/index.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/rules + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/rules your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/rules`)); diff --git a/.yarn/sdks/eslint/lib/types/universal.d.ts b/.yarn/sdks/eslint/lib/types/universal.d.ts new file mode 100644 index 0000000000..662b3f4fd6 --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/universal.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/universal + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/universal your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`)); diff --git a/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts b/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts new file mode 100644 index 0000000000..2e2ccca283 --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/use-at-your-own-risk + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/use-at-your-own-risk your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`)); diff --git a/.yarn/sdks/eslint/lib/universal.js b/.yarn/sdks/eslint/lib/universal.js new file mode 100644 index 0000000000..85a8ccbcea --- /dev/null +++ b/.yarn/sdks/eslint/lib/universal.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/universal + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/universal your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`)); diff --git a/.yarn/sdks/eslint/package.json b/.yarn/sdks/eslint/package.json index 4110fb08c3..ef73eb41d6 100644 --- a/.yarn/sdks/eslint/package.json +++ b/.yarn/sdks/eslint/package.json @@ -1,14 +1,27 @@ { "name": "eslint", - "version": "8.57.1-sdk", + "version": "9.14.0-sdk", "main": "./lib/api.js", "type": "commonjs", "bin": { "eslint": "./bin/eslint.js" }, "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/api.js" + }, "./package.json": "./package.json", - ".": "./lib/api.js", - "./use-at-your-own-risk": "./lib/unsupported-api.js" + "./use-at-your-own-risk": { + "types": "./lib/types/use-at-your-own-risk.d.ts", + "default": "./lib/unsupported-api.js" + }, + "./rules": { + "types": "./lib/types/rules/index.d.ts" + }, + "./universal": { + "types": "./lib/types/universal.d.ts", + "default": "./lib/universal.js" + } } } diff --git a/.yarnrc.yml b/.yarnrc.yml index 137501f5ff..d48407388a 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -18,6 +18,15 @@ packageExtensions: "@swc-node/register@^1.9.0": dependencies: "@swc/types": "*" + "@typescript-eslint/rule-tester@*": + dependencies: + "@typescript-eslint/parser": "~8.12.2" + "angular-eslint@*": + dependencies: + "@typescript-eslint/utils": ~8.12.2 + "@angular-eslint/eslint-plugin-template@*": + dependencies: + "@typescript-eslint/types": "^8.0.0" probot@*: peerDependencies: "@types/express-serve-static-core": ^4.19.5 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d55e7e4637..75d2052894 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ When contributing, please keep in mind the following rules: To ease the process, we are providing a set of: - [Editors configuration](.editorconfig) -- [Linters configuration](./packages/@o3r/eslint-config-otter/README.md) +- [Linters configuration](./packages/@o3r/eslint-config/README.md) - [Component generator](./packages/@o3r/core/README.md#generators) (and more) ### Accelerate your build thanks to Nx Cloud diff --git a/apps/chrome-devtools/.eslintrc.js b/apps/chrome-devtools/.eslintrc.js deleted file mode 100644 index 6784ab10c8..0000000000 --- a/apps/chrome-devtools/.eslintrc.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable quote-props */ - -module.exports = { - 'root': true, - 'overrides': [ - { - 'files': [ - '*.{t,j}s' - ], - 'parserOptions': { - 'tsconfigRootDir': __dirname, - 'project': [ - 'tsconfig.build.json', - 'tsconfig.spec.json', - 'tsconfig.extension.json', - 'tsconfig.eslint.json' - ], - 'sourceType': 'module' - } - }, - { - 'files': [ - '*.html' - ], - 'plugins': [], - 'parser': require.resolve('@angular-eslint/template-parser'), - 'extends': [ - '@o3r/eslint-config-otter/template' - ].map(require.resolve) - } - ], - 'extends': [ - '../../.eslintrc.js' - ] -}; diff --git a/apps/chrome-devtools/eslint.config.mjs b/apps/chrome-devtools/eslint.config.mjs new file mode 100644 index 0000000000..d86dd85d02 --- /dev/null +++ b/apps/chrome-devtools/eslint.config.mjs @@ -0,0 +1,7 @@ +import shared from '../../eslint.shared.config.mjs'; +import local from './eslint.local.config.mjs'; + +export default [ + ...shared, + ...local +]; diff --git a/apps/chrome-devtools/eslint.local.config.mjs b/apps/chrome-devtools/eslint.local.config.mjs new file mode 100644 index 0000000000..5f0124a4e7 --- /dev/null +++ b/apps/chrome-devtools/eslint.local.config.mjs @@ -0,0 +1,45 @@ +import { + dirname, +} from 'node:path'; +import { + fileURLToPath, +} from 'node:url'; +import globals from 'globals'; + +const __filename = fileURLToPath(import.meta.url); +// __dirname is not defined in ES module scope +const __dirname = dirname(__filename); + +export default [ + { + name: '@o3r/chrome-devtools/projects', + languageOptions: { + sourceType: 'module', + parserOptions: { + tsconfigRootDir: __dirname, + project: [ + 'tsconfig.build.json', + 'tsconfig.spec.json', + 'tsconfig.extension.json', + 'tsconfig.eslint.json' + ] + } + } + }, + { + name: '@o3r/chrome-devtools/globals', + languageOptions: { + globals: { + ...globals.webextensions, + globalThis: true + } + } + }, + { + name: '@o3r/chrome-devtools/local', + rules: { + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off' + } + } +]; diff --git a/apps/chrome-devtools/package.json b/apps/chrome-devtools/package.json index 269401a125..957584073c 100644 --- a/apps/chrome-devtools/package.json +++ b/apps/chrome-devtools/package.json @@ -29,41 +29,41 @@ ], "devDependencies": { "@angular-devkit/build-angular": "~18.2.0", - "@angular-eslint/builder": "~18.3.0", - "@angular-eslint/eslint-plugin": "~18.3.0", - "@angular-eslint/eslint-plugin-template": "~18.3.0", - "@angular-eslint/template-parser": "~18.3.0", "@angular/cli": "~18.2.0", "@angular/compiler": "~18.2.0", "@angular/compiler-cli": "~18.2.0", + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0", "@nx/eslint-plugin": "~19.5.0", "@nx/jest": "~19.5.0", "@o3r/build-helpers": "workspace:^", - "@o3r/eslint-config-otter": "workspace:^", + "@o3r/eslint-config": "workspace:^", "@o3r/eslint-plugin": "workspace:^", "@o3r/localization": "workspace:^", "@o3r/logger": "workspace:^", "@o3r/rules-engine": "workspace:^", "@schematics/angular": "~18.2.0", - "@stylistic/eslint-plugin-ts": "~2.4.0", + "@stylistic/eslint-plugin": "~2.7.0", "@types/chrome": "^0.0.283", "@types/jest": "~29.5.2", "@types/tinycolor2": "^1.4.6", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.14.1", - "@typescript-eslint/types": "^7.14.1", - "@typescript-eslint/utils": "^7.14.1", + "@typescript-eslint/parser": "~8.12.2", + "angular-eslint": "~18.4.0", "archiver": "^7.0.0", "chokidar": "^3.5.2", "chrome-webstore-upload": "^3.0.0", "concurrently": "^8.0.0", "cpy-cli": "^5.0.0", - "eslint": "^8.57.0", + "eslint": "~9.14.0", "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import-newlines": "^1.4.0", "eslint-plugin-jest": "~28.8.0", - "eslint-plugin-jsdoc": "~48.11.0", + "eslint-plugin-jsdoc": "~50.2.0", "eslint-plugin-prefer-arrow": "~1.2.3", - "eslint-plugin-unicorn": "^54.0.0", + "eslint-plugin-unicorn": "^56.0.0", + "eslint-plugin-unused-imports": "^4.1.4", + "globals": "^15.9.0", "globby": "^11.1.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", @@ -74,7 +74,8 @@ "nx": "~19.5.0", "rimraf": "^5.0.1", "tsc-watch": "^6.0.4", - "typescript": "~5.5.4" + "typescript": "~5.5.4", + "typescript-eslint": "~8.12.2" }, "dependencies": { "@agnos-ui/angular-bootstrap": "~0.4.1", diff --git a/apps/chrome-devtools/project.json b/apps/chrome-devtools/project.json index a4f7101d49..432291440a 100644 --- a/apps/chrome-devtools/project.json +++ b/apps/chrome-devtools/project.json @@ -138,13 +138,7 @@ "dependsOn": ["^build"] }, "lint": { - "options": { - "eslintConfig": "apps/chrome-devtools/.eslintrc.js", - "lintFilePatterns": [ - "apps/chrome-devtools/src/**/*.ts", - "apps/chrome-devtools/package.json" - ] - } + "executor": "nx:run-commands" }, "test": { "executor": "@nx/jest:jest", diff --git a/apps/chrome-devtools/scripts/publish-to-market.mjs b/apps/chrome-devtools/scripts/publish-to-market.mjs index 2f69a7d3ff..48d9ace9af 100644 --- a/apps/chrome-devtools/scripts/publish-to-market.mjs +++ b/apps/chrome-devtools/scripts/publish-to-market.mjs @@ -1,8 +1,15 @@ +import { + createWriteStream, +} from 'node:fs'; +import { + resolve, +} from 'node:path'; +import * as url from 'node:url'; import archiver from 'archiver'; +// eslint-disable-next-line import/namespace, import/default, import/no-named-as-default, import/no-named-as-default-member -- issue with the parser on this module import chromeWebstoreUpload from 'chrome-webstore-upload'; -import { resolve } from 'node:path'; -import { createWriteStream } from 'node:fs'; -import * as url from 'url'; + +const logger = console; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); @@ -26,15 +33,15 @@ archive.on('end', () => { refreshToken: process.env.CHROME_REFRESH_TOKEN }); store.uploadExisting(buffer).then((resUpload) => { - console.debug(resUpload); + logger.debug(resUpload); store.publish().then((resPublish) => { - console.debug(resPublish); + logger.debug(resPublish); }).catch((err) => { - console.error('Failed to publish Otter Chrome Devtools', err); + logger.error('Failed to publish Otter Chrome Devtools', err); }); }).catch((err) => { - console.error('Failed to upload Otter Chrome Devtools', err); + logger.error('Failed to upload Otter Chrome Devtools', err); }); }); diff --git a/apps/chrome-devtools/scripts/sanitize-extension.cjs b/apps/chrome-devtools/scripts/sanitize-extension.cjs index cd9c880ba1..3a41a288b9 100644 --- a/apps/chrome-devtools/scripts/sanitize-extension.cjs +++ b/apps/chrome-devtools/scripts/sanitize-extension.cjs @@ -1,6 +1,6 @@ -const glob = require('globby'); const { readFile, writeFile } = require('node:fs/promises'); const { resolve } = require('node:path'); +const glob = require('globby'); /** * The Chrome Extension mechanism does not accept exports in loaded module @@ -12,7 +12,7 @@ const removeEmptyExports = async () => { for (const fileRelativePath of files) { const file = resolve(dist, fileRelativePath); let content = await readFile(file, { encoding: 'utf8' }); - content = content.replace(/^export \{\};\r?\n?/mg, ''); + content = content.replace(/^export {};\r?\n?/gm, ''); await writeFile(file, content); } }; diff --git a/apps/chrome-devtools/scripts/sanitize-manifest.cjs b/apps/chrome-devtools/scripts/sanitize-manifest.cjs index 771c7d6717..1c1ad6a465 100644 --- a/apps/chrome-devtools/scripts/sanitize-manifest.cjs +++ b/apps/chrome-devtools/scripts/sanitize-manifest.cjs @@ -4,20 +4,19 @@ const { platform } = require('node:os'); const { join } = require('node:path'); const { watch } = require('chokidar'); const minimist = require('minimist'); +const { version } = require('../package.json'); const manifestPath = join(__dirname, '..', 'dist', 'manifest.json'); const argv = minimist(process.argv.slice(2)); -const { version } = require('../package.json'); /** * Remove $schema field from manifest.json and align version with the package.json - * * @param {string} file path to manifest.json */ const removeSchema = async (file) => { if (existsSync(file)) { const content = JSON.parse(await readFile(file, { encoding: 'utf8' })); - content.version = version.replace(/[a-zA-Z\-].*$/, ''); + content.version = version.replace(/[A-Za-z-].*$/, ''); if (content.$schema) { delete content.$schema; } diff --git a/apps/chrome-devtools/scripts/set-manifest-version.cjs b/apps/chrome-devtools/scripts/set-manifest-version.cjs index 152ca9f41d..83d8da8bac 100644 --- a/apps/chrome-devtools/scripts/set-manifest-version.cjs +++ b/apps/chrome-devtools/scripts/set-manifest-version.cjs @@ -6,7 +6,6 @@ const { version } = require(join(__dirname, '..', 'dist', 'package.json')); /** * align the manifest version with the package.json version - * * @param {string} file path to manifest.json */ const updateVersion = async (file) => { diff --git a/apps/chrome-devtools/src/app-components.main.ts b/apps/chrome-devtools/src/app-components.main.ts index 1674a82ca9..f908634f84 100644 --- a/apps/chrome-devtools/src/app-components.main.ts +++ b/apps/chrome-devtools/src/app-components.main.ts @@ -1,5 +1,9 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { AppComponent } from './app-components/app.component'; +import { + bootstrapApplication, +} from '@angular/platform-browser'; +import { + AppComponent, +} from './app-components/app.component'; -// eslint-disable-next-line no-console +// eslint-disable-next-line no-console -- Generated by Angular bootstrapApplication(AppComponent).catch((err) => console.error(err)); diff --git a/apps/chrome-devtools/src/app-components/app.component.ts b/apps/chrome-devtools/src/app-components/app.component.ts index ee2b8346c8..26ac6a3116 100644 --- a/apps/chrome-devtools/src/app-components/app.component.ts +++ b/apps/chrome-devtools/src/app-components/app.component.ts @@ -1,25 +1,58 @@ -import { AsyncPipe } from '@angular/common'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, +} from '@angular/core'; import { getAnalyticEvents as devkitGetAnalyticEvents, getTranslations as devkitGetTranslations, getAnalyticEventsRec, getTranslationsRec, Ng, - OtterLikeComponentInfo + OtterLikeComponentInfo, } from '@o3r/components'; -import type { ConfigurationModel } from '@o3r/configuration'; -import {otterComponentInfoPropertyName} from '@o3r/core'; import type { - OtterComponentInfo + ConfigurationModel, +} from '@o3r/configuration'; +import { + otterComponentInfoPropertyName, +} from '@o3r/core'; +import type { + OtterComponentInfo, } from '@o3r/core'; -import type { RulesetExecutionDebug } from '@o3r/rules-engine'; -import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { filter, map, startWith } from 'rxjs/operators'; -import { AppConnectionComponent } from '../components/app-connection/app-connection.component'; -import { OtterComponentComponent } from '../components/otter-component/otter-component.component'; -import { RulesetHistoryService } from '../services/ruleset-history.service'; -import { ChromeExtensionConnectionService } from '../services/connection.service'; +import type { + RulesetExecutionDebug, +} from '@o3r/rules-engine'; +import { + BehaviorSubject, + combineLatest, + Observable, +} from 'rxjs'; +import { + filter, + map, + startWith, +} from 'rxjs/operators'; +import { + AppConnectionComponent, +} from '../components/app-connection/app-connection.component'; +import { + OtterComponentComponent, +} from '../components/otter-component/otter-component.component'; +import { + ChromeExtensionConnectionService, +} from '../services/connection.service'; +import { + RulesetHistoryService, +} from '../services/ruleset-history.service'; + +declare namespace window { + let ng: Ng | undefined; + let $0: Element | undefined; +} /** * Retrieve component information @@ -28,12 +61,13 @@ import { ChromeExtensionConnectionService } from '../services/connection.service * @param getAnalyticEvents Function to retrieve analytic events */ function getSelectedComponentInfo(getTranslations: typeof devkitGetTranslations, getAnalyticEvents: typeof devkitGetAnalyticEvents): OtterLikeComponentInfo | undefined { - const angularDevTools: Ng | undefined = (window as any).ng; - const selectedElement = (window as any).$0; + const angularDevTools = window.ng; + const selectedElement = window.$0; const o3rInfoProperty: typeof otterComponentInfoPropertyName = '__otter-info__'; if (!angularDevTools || !selectedElement) { return; } + /* eslint-disable @typescript-eslint/no-unsafe-argument -- expected type is `any` */ let componentClassInstance = angularDevTools.getComponent(selectedElement) || angularDevTools.getOwningComponent(selectedElement); let info: OtterLikeComponentInfo | undefined; @@ -42,7 +76,7 @@ function getSelectedComponentInfo(getTranslations: typeof devkitGetTranslations, return; } do { - compInfo = componentClassInstance[o3rInfoProperty]; + compInfo = componentClassInstance[o3rInfoProperty] as OtterComponentInfo | undefined; if (compInfo) { info = { configId: compInfo.configId, @@ -54,6 +88,8 @@ function getSelectedComponentInfo(getTranslations: typeof devkitGetTranslations, componentClassInstance = angularDevTools.getOwningComponent(componentClassInstance); } } while (!compInfo && componentClassInstance); + /* eslint-enable-next-line @typescript-eslint/no-unsafe-assignment */ + return info; } @@ -103,23 +139,22 @@ export class AppComponent { ]).pipe( map(([info, executions]) => executions.filter((execution) => - (execution.rulesetInformation?.linkedComponent?.name === info.componentName) || - (execution.rulesetInformation?.linkedComponents?.or.some(linkedComp => linkedComp.name === info.componentName)) + (execution.rulesetInformation?.linkedComponent?.name === info.componentName) + || (execution.rulesetInformation?.linkedComponents?.or.some((linkedComp) => linkedComp.name === info.componentName)) ) ) ); } private requestSelectedComponentInfo() { + /* eslint-disable @typescript-eslint/restrict-template-expressions -- we want to print the content of the functions */ chrome.devtools.inspectedWindow.eval( - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `function getTranslations(node){ return (${getTranslationsRec})(node, getTranslations); } ` + - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `function getAnalyticEvents(node){ return (${getAnalyticEventsRec})(node, getAnalyticEvents); } ` + - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `(${getSelectedComponentInfo})(getTranslations, getAnalyticEvents);`, + `function getTranslations(node){ return (${getTranslationsRec})(node, getTranslations); } ` + + `function getAnalyticEvents(node){ return (${getAnalyticEventsRec})(node, getAnalyticEvents); } ` + + `(${getSelectedComponentInfo})(getTranslations, getAnalyticEvents);`, this.updateSelectedComponentInfoCallback ); + /* eslint-enable @typescript-eslint/restrict-template-expressions */ } private updateSelectedComponentInfo(info?: OtterLikeComponentInfo) { diff --git a/apps/chrome-devtools/src/app-devtools.main.ts b/apps/chrome-devtools/src/app-devtools.main.ts index 1da99a2b98..4fe5cbca28 100644 --- a/apps/chrome-devtools/src/app-devtools.main.ts +++ b/apps/chrome-devtools/src/app-devtools.main.ts @@ -1,5 +1,9 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { AppComponent } from './app-devtools/app.component'; +import { + bootstrapApplication, +} from '@angular/platform-browser'; +import { + AppComponent, +} from './app-devtools/app.component'; -// eslint-disable-next-line no-console +// eslint-disable-next-line no-console -- Generated by Angular bootstrapApplication(AppComponent).catch((err) => console.error(err)); diff --git a/apps/chrome-devtools/src/app-devtools/app.component.ts b/apps/chrome-devtools/src/app-devtools/app.component.ts index 7026e87a31..b52f62efb7 100644 --- a/apps/chrome-devtools/src/app-devtools/app.component.ts +++ b/apps/chrome-devtools/src/app-devtools/app.component.ts @@ -1,22 +1,71 @@ -import { AsyncPipe, JsonPipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormBuilder, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DfSelectModule, DfTooltipModule } from '@design-factory/design-factory'; -import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; -import { RulesetHistoryPresModule } from '@o3r/rules-engine'; -import { AppConnectionComponent } from '../components/app-connection/app-connection.component'; -import type { State } from '../extension/interface'; -import { StateService } from '../services'; -import { ChromeExtensionConnectionService, isApplicationInformationMessage } from '../services/connection.service'; -import { RulesetHistoryService } from '../services/ruleset-history.service'; -import { ComponentPanelPresComponent } from './component-panel/component-panel-pres.component'; -import { ConfigPanelPresComponent } from './config-panel/config-panel-pres.component'; -import { DebugPanelPresComponent } from './debug-panel/debug-panel-pres.component'; -import { DebugPanelService } from './debug-panel/debug-panel.service'; -import { LocalizationPanelPresComponent } from './localization-panel/localization-panel-pres.component'; -import { StatePanelComponent } from './state-panel/state-panel.component'; -import { ThemingPanelPresComponent } from './theming-panel/theming-panel-pres.component'; +import { + AsyncPipe, + JsonPipe, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, +} from '@angular/core'; +import { + takeUntilDestroyed, + toSignal, +} from '@angular/core/rxjs-interop'; +import { + FormBuilder, + FormControl, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { + DfSelectModule, + DfTooltipModule, +} from '@design-factory/design-factory'; +import { + NgbNavModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { + RulesetHistoryPresModule, +} from '@o3r/rules-engine'; +import { + AppConnectionComponent, +} from '../components/app-connection/app-connection.component'; +import type { + State, +} from '../extension/interface'; +import { + StateService, +} from '../services'; +import { + ChromeExtensionConnectionService, + isApplicationInformationMessage, +} from '../services/connection.service'; +import { + RulesetHistoryService, +} from '../services/ruleset-history.service'; +import { + ComponentPanelPresComponent, +} from './component-panel/component-panel-pres.component'; +import { + ConfigPanelPresComponent, +} from './config-panel/config-panel-pres.component'; +import { + DebugPanelPresComponent, +} from './debug-panel/debug-panel-pres.component'; +import { + DebugPanelService, +} from './debug-panel/debug-panel.service'; +import { + LocalizationPanelPresComponent, +} from './localization-panel/localization-panel-pres.component'; +import { + StatePanelComponent, +} from './state-panel/state-panel.component'; +import { + ThemingPanelPresComponent, +} from './theming-panel/theming-panel-pres.component'; @Component({ selector: 'app-root', diff --git a/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.component.ts b/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.component.ts index 3f51cd73ab..a51bbe63a0 100644 --- a/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.component.ts +++ b/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.component.ts @@ -1,13 +1,45 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, ViewEncapsulation } from '@angular/core'; -import type { IsComponentSelectionAvailableMessage, OtterLikeComponentInfo } from '@o3r/components'; -import { ConfigurationModel } from '@o3r/configuration'; -import type { RulesetExecutionDebug } from '@o3r/rules-engine'; -import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; -import { filter, map, shareReplay, startWith } from 'rxjs/operators'; -import { OtterComponentComponent } from '../../components/otter-component/otter-component.component'; -import { RulesetHistoryService } from '../../services/ruleset-history.service'; -import { ChromeExtensionConnectionService, isSelectedComponentInfoMessage } from '../../services/connection.service'; -import { AsyncPipe } from '@angular/common'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + ViewEncapsulation, +} from '@angular/core'; +import type { + IsComponentSelectionAvailableMessage, + OtterLikeComponentInfo, +} from '@o3r/components'; +import { + ConfigurationModel, +} from '@o3r/configuration'; +import type { + RulesetExecutionDebug, +} from '@o3r/rules-engine'; +import { + BehaviorSubject, + combineLatest, + Observable, + Subscription, +} from 'rxjs'; +import { + filter, + map, + shareReplay, + startWith, +} from 'rxjs/operators'; +import { + OtterComponentComponent, +} from '../../components/otter-component/otter-component.component'; +import { + ChromeExtensionConnectionService, + isSelectedComponentInfoMessage, +} from '../../services/connection.service'; +import { + RulesetHistoryService, +} from '../../services/ruleset-history.service'; @Component({ selector: 'o3r-component-panel-pres', @@ -54,7 +86,7 @@ export class ComponentPanelPresComponent implements OnDestroy { map((data) => data.available), startWith(false) ); - const selectedComponentInfoMessage$ = connectionService.message$.pipe(filter(isSelectedComponentInfoMessage), shareReplay({bufferSize: 1, refCount: true})); + const selectedComponentInfoMessage$ = connectionService.message$.pipe(filter(isSelectedComponentInfoMessage), shareReplay({ bufferSize: 1, refCount: true })); this.hasContainer$ = selectedComponentInfoMessage$.pipe(map((info) => !!info.container)); this.subscription.add( selectedComponentInfoMessage$.subscribe((info) => { @@ -67,7 +99,7 @@ export class ComponentPanelPresComponent implements OnDestroy { this.isLookingToContainer$ ]).pipe( map(([info, isLookingToContainer]) => isLookingToContainer ? info.container : info), - shareReplay({bufferSize: 1, refCount: true}) + shareReplay({ bufferSize: 1, refCount: true }) ); this.config$ = combineLatest([ @@ -83,8 +115,8 @@ export class ComponentPanelPresComponent implements OnDestroy { ]).pipe( map(([info, executions]) => executions.filter((execution) => - (execution.rulesetInformation?.linkedComponent?.name === info.componentName) || - (execution.rulesetInformation?.linkedComponents?.or.some(linkedComp => linkedComp.name === info.componentName)) + (execution.rulesetInformation?.linkedComponent?.name === info.componentName) + || (execution.rulesetInformation?.linkedComponents?.or.some((linkedComp) => linkedComp.name === info.componentName)) ) ) ); diff --git a/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.template.html b/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.template.html index d2f9207ff6..a6e862980a 100644 --- a/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.template.html +++ b/apps/chrome-devtools/src/app-devtools/component-panel/component-panel-pres.template.html @@ -1,6 +1,8 @@ @if (isComponentSelectionAvailable$ | async) {
- + @if ((isLookingToContainer$ | async) || (selectedComponentInfo$ | async)?.container) { }
{{localization.description}}
diff --git a/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.component.ts b/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.component.ts index 72034ec9be..4070ed17c8 100644 --- a/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.component.ts +++ b/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.component.ts @@ -1,4 +1,9 @@ -import { DOCUMENT, JsonPipe, KeyValuePipe, NgClass } from '@angular/common'; +import { + DOCUMENT, + JsonPipe, + KeyValuePipe, + NgClass, +} from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -9,15 +14,40 @@ import { type Signal, untracked, viewChild, - ViewEncapsulation, WritableSignal + ViewEncapsulation, + WritableSignal, } from '@angular/core'; -import { type AbstractControl, FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, type ValidationErrors, type ValidatorFn, Validators } from '@angular/forms'; -import { DfTooltipModule, DfTriggerClickDirective } from '@design-factory/design-factory'; -import { StateService } from '../../services'; -import { getBestColorContrast } from '../theming-panel/color.helpers'; -import type { State } from '../../extension/interface'; -import { toSignal } from '@angular/core/rxjs-interop'; -import { combineLatest, map } from 'rxjs'; +import { + toSignal, +} from '@angular/core/rxjs-interop'; +import { + type AbstractControl, + FormBuilder, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + type ValidationErrors, + type ValidatorFn, + Validators, +} from '@angular/forms'; +import { + DfTooltipModule, + DfTriggerClickDirective, +} from '@design-factory/design-factory'; +import { + combineLatest, + map, +} from 'rxjs'; +import type { + State, +} from '../../extension/interface'; +import { + StateService, +} from '../../services'; +import { + getBestColorContrast, +} from '../theming-panel/color.helpers'; type StateForm = { name: FormControl; @@ -47,6 +77,7 @@ const duplicateNameValidator: ValidatorFn = (control: AbstractControl): return null; }; +// eslint-disable-next-line @typescript-eslint/unbound-method -- Validators are bound methods const stateNameValidators = [Validators.required, duplicateNameValidator]; const createStateForm = (name: string, color?: string | null) => new FormGroup({ @@ -144,9 +175,9 @@ export class StatePanelComponent { newStateNameControl.statusChanges ]).pipe( map(() => newStateNameControl.errors - ? newStateNameControl.errors.required + ? (newStateNameControl.errors.required ? 'Please provide a state name.' - : 'Please provide a unique name.' + : 'Please provide a unique name.') : null ) ), @@ -269,7 +300,7 @@ export class StatePanelComponent { ) ) ) { - if (Object.keys(this.states()).some((stateName) => stateName === state.name)) { + if (Object.keys(this.states()).includes(state.name)) { throw new Error(`${state.name} already exists`); } this.stateService.updateState( diff --git a/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.template.html b/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.template.html index 511c881921..83335ced88 100644 --- a/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.template.html +++ b/apps/chrome-devtools/src/app-devtools/state-panel/state-panel.template.html @@ -39,18 +39,23 @@ + @let updateText = activeState()?.name !== state.key ? 'Only the selected state can be saved' : 'Save changes'; + @let deleteText = activeState()?.name === state.key ? 'Selected state cannot be deleted' : 'Delete'; diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/color.helpers.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/color.helpers.ts index 1adf7316e3..b4302f2bf6 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/color.helpers.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/color.helpers.ts @@ -5,20 +5,20 @@ import TinyColor from 'tinycolor2'; * Material palette variants */ export enum PaletteVariant { - 'V50' = '50', - 'V100' = '100', - 'V200' = '200', - 'V300' = '300', - 'V400' = '400', - 'V500' = '500', - 'V600' = '600', - 'V700' = '700', - 'V800' = '800', - 'V900' = '900', - 'A100' = 'A100', - 'A200' = 'A200', - 'A400' = 'A400', - 'A700' = 'A700' + V50 = '50', + V100 = '100', + V200 = '200', + V300 = '300', + V400 = '400', + V500 = '500', + V600 = '600', + V700 = '700', + V800 = '800', + V900 = '900', + A100 = 'A100', + A200 = 'A200', + A400 = 'A400', + A700 = 'A700' } /** @@ -26,41 +26,39 @@ export enum PaletteVariant { */ export const DEFAULT_PALETTE_VARIANT: PaletteVariant = PaletteVariant.V500; -/* eslint-disable @typescript-eslint/naming-convention */ const SATURATION_VALUES: Record = { - '50': 0.91, - '100': 0.98, - '200': 0.96, - '300': 0.95, - '400': 0.96, - '500': 1, - '600': 1, - '700': 0.99, - '800': 0.89, - '900': 0.86, - 'A100': 1, - 'A200': 1, - 'A400': 1, - 'A700': 1 + 50: 0.91, + 100: 0.98, + 200: 0.96, + 300: 0.95, + 400: 0.96, + 500: 1, + 600: 1, + 700: 0.99, + 800: 0.89, + 900: 0.86, + A100: 1, + A200: 1, + A400: 1, + A700: 1 }; const LIGHTNESS_VALUES: Record = { - '50': 0.12, - '100': 0.3, - '200': 0.5, - '300': 0.7, - '400': 0.86, - '500': 1, - '600': 0.87, - '700': 0.66, - '800': 0.45, - '900': 0.16, - 'A100': 0.76, - 'A200': 0.64, - 'A400': 0.49, - 'A700': 0.44 + 50: 0.12, + 100: 0.3, + 200: 0.5, + 300: 0.7, + 400: 0.86, + 500: 1, + 600: 0.87, + 700: 0.66, + 800: 0.45, + 900: 0.16, + A100: 0.76, + A200: 0.64, + A400: 0.49, + A700: 0.44 }; -/* eslint-enable @typescript-eslint/naming-convention */ /** * Returns palette colors from one color @@ -109,11 +107,7 @@ export const getAccessibilityContrastScore = (color1: string, color2: string, te if (readability >= 7) { return 'AAA'; } else if (readability >= 4.5) { - if (textSize === 'small') { - return 'AA'; - } else { - return 'AAA'; - } + return textSize === 'small' ? 'AA' : 'AAA'; } else if (readability >= 3 && textSize === 'large') { return 'AA'; } diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/color.pipe.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/color.pipe.ts index bbe20c956f..2cdd441849 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/color.pipe.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/color.pipe.ts @@ -1,6 +1,12 @@ -import { Pipe, type PipeTransform } from '@angular/core'; +import { + Pipe, + type PipeTransform, +} from '@angular/core'; import TinyColor from 'tinycolor2'; -import { getAccessibilityContrastScore, getBestColorContrast } from './color.helpers'; +import { + getAccessibilityContrastScore, + getBestColorContrast, +} from './color.helpers'; /** * Convert the color to hexadecimal format diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/common.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/common.ts index 5736ef21cd..e5a3230114 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/common.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/common.ts @@ -1,7 +1,9 @@ -import type { StylingVariable } from '@o3r/styling'; +import type { + StylingVariable, +} from '@o3r/styling'; /** RegExp to find a variable and get the variable name in the first group */ -export const varRegExp = /^var\(--([^, )]*).*\)$/; +export const varRegExp = /^var\(--([^ ),]*).*\)$/; /** * Is the variable value a reference to another variable @@ -16,7 +18,7 @@ export const isRef = (variableValue: string) => varRegExp.test(variableValue); */ export const searchFn = (variable: StylingVariable, search: string) => [variable.name, variable.category, variable.description, ...(variable.tags || []), variable.defaultValue] - .some((value) => value?.toLowerCase().includes(search.replace(/var\(--|[, )]/g, ''))); + .some((value) => value?.toLowerCase().includes(search.replace(/var\(--|[ ),]/g, ''))); /** * Find the value of a variable @@ -37,7 +39,7 @@ export const resolveVariable = ( const variableValue = runtimeValueVariables?.[variableName] ?? variableMetadata?.runtimeValue ?? variableMetadata?.defaultValue ?? ''; if (isRef(variableValue)) { const varName = variableValue.match(varRegExp)![1]; - return !visitedVariables.has(varName) ? resolveVariable(varName, runtimeValueVariables, variables, visitedVariables) : undefined; + return visitedVariables.has(varName) ? undefined : resolveVariable(varName, runtimeValueVariables, variables, visitedVariables); } return variableValue; }; diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/is-ref.pipe.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/is-ref.pipe.ts index 44a870b49c..f0ace3b5f7 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/is-ref.pipe.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/is-ref.pipe.ts @@ -1,6 +1,13 @@ -import { Pipe, type PipeTransform } from '@angular/core'; -import type { StylingVariable } from '@o3r/styling'; -import { isRef } from './common'; +import { + Pipe, + type PipeTransform, +} from '@angular/core'; +import type { + StylingVariable, +} from '@o3r/styling'; +import { + isRef, +} from './common'; @Pipe({ name: 'isRef', diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/memoize.pipe.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/memoize.pipe.ts index f16619296c..6d1dde67c8 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/memoize.pipe.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/memoize.pipe.ts @@ -1,8 +1,12 @@ -import { Pipe, type PipeTransform } from '@angular/core'; +import { + Pipe, + type PipeTransform, +} from '@angular/core'; @Pipe({ name: 'memoize', standalone: true }) export class MemoizePipe implements PipeTransform { public transform(fn: (...args: any[]) => any, ...pipeArgs: any[]): any { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- type of pipeArgs is `any[]` return fn(...pipeArgs); } } diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.component.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.component.ts index 9976bee1a0..13e62ecb66 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.component.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.component.ts @@ -1,20 +1,81 @@ -import { ChangeDetectionStrategy, Component, computed, effect, inject, type OnDestroy, type Signal, untracked, ViewEncapsulation } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DfTooltipModule } from '@design-factory/design-factory'; -import { NgbAccordionModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; -import { computeItemIdentifier } from '@o3r/core'; -import { type GetStylingVariableContentMessage, PALETTE_TAG_NAME, type StylingVariable, THEME_TAG_NAME } from '@o3r/styling'; -import { combineLatest, Observable, Subscription } from 'rxjs'; -import { map, startWith, throttleTime } from 'rxjs/operators'; -import { ChromeExtensionConnectionService, filterAndMapMessage, StateService } from '../../services'; -import { DEFAULT_PALETTE_VARIANT, getPaletteColors } from './color.helpers'; -import { AccessibilityConstrastScorePipe, ConstrastPipe, HexColorPipe } from './color.pipe'; -import { getVariant, resolveVariable, searchFn } from './common'; -import { IsRefPipe } from './is-ref.pipe'; -import { MemoizePipe } from './memoize.pipe'; -import { VariableLabelPipe } from './variable-label.pipe'; -import { VariableNamePipe } from './variable-name.pipe'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, + type OnDestroy, + type Signal, + untracked, + ViewEncapsulation, +} from '@angular/core'; +import { + takeUntilDestroyed, + toSignal, +} from '@angular/core/rxjs-interop'; +import { + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, +} from '@angular/forms'; +import { + DfTooltipModule, +} from '@design-factory/design-factory'; +import { + NgbAccordionModule, + NgbTypeaheadModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { + computeItemIdentifier, +} from '@o3r/core'; +import { + type GetStylingVariableContentMessage, + PALETTE_TAG_NAME, + type StylingVariable, + THEME_TAG_NAME, +} from '@o3r/styling'; +import { + combineLatest, + Observable, + Subscription, +} from 'rxjs'; +import { + map, + startWith, + throttleTime, +} from 'rxjs/operators'; +import { + ChromeExtensionConnectionService, + filterAndMapMessage, + StateService, +} from '../../services'; +import { + DEFAULT_PALETTE_VARIANT, + getPaletteColors, +} from './color.helpers'; +import { + AccessibilityConstrastScorePipe, + ConstrastPipe, + HexColorPipe, +} from './color.pipe'; +import { + getVariant, + resolveVariable, + searchFn, +} from './common'; +import { + IsRefPipe, +} from './is-ref.pipe'; +import { + MemoizePipe, +} from './memoize.pipe'; +import { + VariableLabelPipe, +} from './variable-label.pipe'; +import { + VariableNamePipe, +} from './variable-name.pipe'; const THROTTLE_TIME = 100; @@ -64,8 +125,11 @@ export interface VariableGroup { }) export class ThemingPanelPresComponent implements OnDestroy { private readonly stateService = inject(StateService); + public readonly activeStateName = computed(() => this.stateService.activeState()?.name); + public readonly themingActiveStateOverrides = computed(() => this.stateService.activeState()?.stylingVariables || {}); + public readonly themingLocalStateOverrides = computed(() => this.stateService.localState()?.stylingVariables || {}); public readonly resolvedVariables: Signal>; public readonly variablesMap: Signal>; @@ -112,7 +176,9 @@ export class ThemingPanelPresComponent implements OnDestroy { variables.forEach((variable) => { const initialValue = variable.runtimeValue ?? variable.defaultValue; const control = variablesControl.controls[variable.name]; - if (!control) { + if (control) { + control.setValue(initialValue, { emitEvent: false }); + } else { const newControl = new FormControl(initialValue); variablesControl.addControl(variable.name, newControl); this.subscription.add( @@ -120,7 +186,7 @@ export class ThemingPanelPresComponent implements OnDestroy { throttleTime(THROTTLE_TIME, undefined, { trailing: true }) ).subscribe((newValue) => { const update = { - [variable.name]: (newValue !== variable.defaultValue ? newValue : null) ?? null + [variable.name]: (newValue === variable.defaultValue ? null : newValue) ?? null }; if (update[variable.name] !== null) { this.stateService.updateLocalState({ @@ -132,8 +198,6 @@ export class ThemingPanelPresComponent implements OnDestroy { } }) ); - } else { - control.setValue(initialValue, { emitEvent: false }); } }); }); @@ -248,7 +312,7 @@ export class ThemingPanelPresComponent implements OnDestroy { && currentVariable.type === variable.type && typeof resolveVariable(variable.name, runtimeValues, variables) !== 'undefined' && searchFn(variable, term)) - .map(({name}: StylingVariable) => `var(--${name})`) + .map(({ name }: StylingVariable) => `var(--${name})`) : []) ); @@ -300,7 +364,6 @@ export class ThemingPanelPresComponent implements OnDestroy { /** * Handler for palette color reset * @param palette - * @param resetValue * @param event */ public onPaletteReset(palette: VariableGroup, event: UIEvent) { diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.template.html b/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.template.html index 84a723974e..f6aef79336 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.template.html +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/theming-panel-pres.template.html @@ -28,22 +28,22 @@

} } @else { - + }

@@ -97,14 +97,13 @@

@if (themingActiveStateOverrides()[variable.name] || themingLocalStateOverrides()[variable.name]) { - - + @let text = activeStateName() && themingActiveStateOverrides()[variable.name] !== themingLocalStateOverrides()[variable.name] + ? 'Reset value to the one from ' + activeStateName() + ': ' + (themingActiveStateOverrides()[variable.name] || variable.defaultValue) + : 'Reset value to the default one from metadata: ' + variable.defaultValue; + } @if (variable.description) { diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/variable-label.pipe.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/variable-label.pipe.ts index 2e40838638..0d9bf6b328 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/variable-label.pipe.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/variable-label.pipe.ts @@ -1,5 +1,10 @@ -import { Pipe, type PipeTransform } from '@angular/core'; -import type { StylingVariable } from '@o3r/styling'; +import { + Pipe, + type PipeTransform, +} from '@angular/core'; +import type { + StylingVariable, +} from '@o3r/styling'; @Pipe({ name: 'variableLabel', diff --git a/apps/chrome-devtools/src/app-devtools/theming-panel/variable-name.pipe.ts b/apps/chrome-devtools/src/app-devtools/theming-panel/variable-name.pipe.ts index a6b099eb37..ef305d2b40 100644 --- a/apps/chrome-devtools/src/app-devtools/theming-panel/variable-name.pipe.ts +++ b/apps/chrome-devtools/src/app-devtools/theming-panel/variable-name.pipe.ts @@ -1,5 +1,10 @@ -import { Pipe, type PipeTransform } from '@angular/core'; -import { varRegExp } from './common'; +import { + Pipe, + type PipeTransform, +} from '@angular/core'; +import { + varRegExp, +} from './common'; @Pipe({ name: 'variableName', diff --git a/apps/chrome-devtools/src/components/app-connection/app-connection.component.ts b/apps/chrome-devtools/src/components/app-connection/app-connection.component.ts index 05f69d4802..be14216405 100644 --- a/apps/chrome-devtools/src/components/app-connection/app-connection.component.ts +++ b/apps/chrome-devtools/src/components/app-connection/app-connection.component.ts @@ -1,7 +1,20 @@ -import { AsyncPipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, inject, OnDestroy } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; -import { AppState, ChromeExtensionConnectionService } from '../../services/connection.service'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + inject, + OnDestroy, +} from '@angular/core'; +import { + Observable, + Subscription, +} from 'rxjs'; +import { + AppState, + ChromeExtensionConnectionService, +} from '../../services/connection.service'; @Component({ selector: 'app-connection', diff --git a/apps/chrome-devtools/src/components/config-form/config-form.component.ts b/apps/chrome-devtools/src/components/config-form/config-form.component.ts index fadb784a63..50a7012707 100644 --- a/apps/chrome-devtools/src/components/config-form/config-form.component.ts +++ b/apps/chrome-devtools/src/components/config-form/config-form.component.ts @@ -1,9 +1,30 @@ -import { KeyValuePipe, NgClass } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'; -import { FormControl, FormsModule, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; -import { ConfigurationModel } from '@o3r/configuration'; -import { ChromeExtensionConnectionService } from '../../services/connection.service'; -import { StateService } from '../../services'; +import { + KeyValuePipe, + NgClass, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + input, +} from '@angular/core'; +import { + FormControl, + FormsModule, + ReactiveFormsModule, + UntypedFormGroup, +} from '@angular/forms'; +import type { + Configuration, + ConfigurationModel, +} from '@o3r/configuration'; +import { + StateService, +} from '../../services'; +import { + ChromeExtensionConnectionService, +} from '../../services/connection.service'; type ControlsType = Record; @@ -33,6 +54,7 @@ export class ConfigFormComponent { /** * Type of controls for each configuration property */ + public controlsType = computed>(() => { return Object.entries(this.config()).reduce((acc: ControlsType, [key, value]) => { if (key !== 'id') { @@ -45,6 +67,7 @@ export class ConfigFormComponent { } this.form.controls[key].setValue(value); } else { + // eslint-disable-next-line no-console -- needed to warn the user console.warn(`[Otter Chrome Extension] Unsupported type: ${type}`); } } @@ -58,12 +81,12 @@ export class ConfigFormComponent { public onSubmit() { void this.stateService.updateLocalState({ configurations: { - [this.config().id]: this.form.value + [this.config().id]: this.form.value as Configuration } }); this.connectionService.sendMessage('updateConfig', { id: this.config().id, - configValue: this.form.value + configValue: this.form.value as Configuration }); } } diff --git a/apps/chrome-devtools/src/components/otter-component/otter-component.component.ts b/apps/chrome-devtools/src/components/otter-component/otter-component.component.ts index ca5cedeb77..35948eed59 100644 --- a/apps/chrome-devtools/src/components/otter-component/otter-component.component.ts +++ b/apps/chrome-devtools/src/components/otter-component/otter-component.component.ts @@ -1,13 +1,35 @@ -import { KeyValuePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, Input, OnChanges, Pipe, PipeTransform, SimpleChanges } from '@angular/core'; -import { NgbAccordionModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; -import type { OtterLikeComponentInfo } from '@o3r/components'; -import type { ConfigurationModel } from '@o3r/configuration'; -import { type RulesetExecutionDebug, RulesetHistoryPresModule } from '@o3r/rules-engine'; -import { ConfigFormComponent } from '../config-form/config-form.component'; +import { + KeyValuePipe, +} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + Pipe, + PipeTransform, + SimpleChanges, +} from '@angular/core'; +import { + NgbAccordionModule, + NgbNavModule, +} from '@ng-bootstrap/ng-bootstrap'; +import type { + OtterLikeComponentInfo, +} from '@o3r/components'; +import type { + ConfigurationModel, +} from '@o3r/configuration'; +import { + type RulesetExecutionDebug, + RulesetHistoryPresModule, +} from '@o3r/rules-engine'; +import { + ConfigFormComponent, +} from '../config-form/config-form.component'; const isRecordOfArray = (value?: object | null): value is Record => { - return value ? Object.values(value || {}).every(Array.isArray) : false; + return value ? Object.values(value || {}).every((element) => Array.isArray(element)) : false; }; @Pipe({ @@ -33,15 +55,16 @@ export class NbPropPipe implements PipeTransform { }) export class ListPipe implements PipeTransform { public transform(value: string[]) { - return value.length > 1 ? `${ - value.splice(0, -1).join(', ') - } and ${ - value[value.length - 1] - }` : value[0] || ''; + return value.length > 1 + ? `${ + value.splice(0, -1).join(', ') + } and ${ + value.at(-1) + }` + : value[0] || ''; } } - @Component({ selector: 'app-otter-component', templateUrl: './otter-component.component.html', @@ -85,7 +108,7 @@ export class OtterComponentComponent implements OnChanges { .flatMap((execution) => { const linkedComponent = execution.rulesetInformation?.linkedComponent; const linkedComponents = execution.rulesetInformation?.linkedComponents?.or; - return (linkedComponents?.map(lc => lc.library) || [linkedComponent?.library]).filter((lib): lib is string => !!lib); + return (linkedComponents?.map((lc) => lc.library) || [linkedComponent?.library]).filter((lib): lib is string => !!lib); }) ) )]; diff --git a/apps/chrome-devtools/src/extension/background.ts b/apps/chrome-devtools/src/extension/background.ts index 55279e3e4c..022cd71084 100644 --- a/apps/chrome-devtools/src/extension/background.ts +++ b/apps/chrome-devtools/src/extension/background.ts @@ -1,8 +1,22 @@ -import type { InjectContentMessage, OtterMessage, OtterMessageContent, otterMessageType } from '@o3r/core'; -import type { ApplicationInformationContentMessage } from '@o3r/application'; -import type { scriptToInject as ScriptToInject } from '../services/connection.service'; -import type { ACTIVE_STATE_NAME_KEY as ActivateStateNameKey, ExtensionMessage, State, STATES_KEY as StatesKey, WHITELISTED_HOSTS_KEY as WhitelistedHostsKey } from './interface'; - +import type { + ApplicationInformationContentMessage, +} from '@o3r/application'; +import type { + InjectContentMessage, + OtterMessage, + OtterMessageContent, + otterMessageType, +} from '@o3r/core'; +import type { + scriptToInject as ScriptToInject, +} from '../shared/index'; +import type { + ACTIVE_STATE_NAME_KEY as ActivateStateNameKey, + ExtensionMessage, + State, + STATES_KEY as StatesKey, + WHITELISTED_HOSTS_KEY as WhitelistedHostsKey, +} from './interface'; /** Type of a message exchanged with the Otter Chrome DevTools extension */ const postMessageType: typeof otterMessageType = 'otter'; @@ -18,7 +32,7 @@ const WHITELISTED_HOSTS_KEY: typeof WhitelistedHostsKey = 'WHITELISTED_HOSTS'; * @param message */ const isOtterDebugMessage = (message: any): message is OtterMessage => { - return message?.type === postMessageType; + return (message as OtterMessage | undefined)?.type === postMessageType; }; /** @@ -26,7 +40,7 @@ const isOtterDebugMessage = (message: any): message is OtterMessage => { * @param message */ const isExtensionMessage = (message: any): message is ExtensionMessage => { - return typeof message?.tabId !== 'undefined'; + return typeof (message as ExtensionMessage | undefined)?.tabId !== 'undefined'; }; /** @@ -34,7 +48,7 @@ const isExtensionMessage = (message: any): message is ExtensionMessage => { * @param content */ const isInjectionContentMessage = (content: any): content is InjectContentMessage => { - return content?.dataType === 'inject'; + return (content as InjectContentMessage | undefined)?.dataType === 'inject'; }; /** @@ -53,11 +67,23 @@ const isWhitelistedHost = async (url?: string) => { if (!url) { return; } - const whitelistHosts = (await chrome.storage.sync.get(WHITELISTED_HOSTS_KEY))[WHITELISTED_HOSTS_KEY] as string[] || ['localhost']; + const { [WHITELISTED_HOSTS_KEY]: whitelistHosts = ['localhost'] } = (await chrome.storage.sync.get>(WHITELISTED_HOSTS_KEY)); const { hostname } = new URL(url); return whitelistHosts.some((host) => (new RegExp(host)).test(hostname)); }; +function postMessageWithDataTypeToEveryone(dataType: string, content: string, type: typeof postMessageType) { + window.postMessage({ + type, + to: 'app', + + content: { + ...JSON.parse(content), + dataType: dataType + } + }, '*'); +} + /** * Retrieve a state and send a message to the Otter application connected to the DevTool that they should apply this * state. @@ -69,33 +95,24 @@ const applyActivateState = async (appName: string, tabId: number) => { await chrome.scripting.executeScript({ target: { tabId }, args: [dataType, JSON.stringify(content), postMessageType], - // eslint-disable-next-line prefer-arrow/prefer-arrow-functions, @typescript-eslint/no-shadow - func: function (dataType, content, postMessageType) { - window.postMessage({ - type: postMessageType, - to: 'app', - content: { - ...JSON.parse(content), - dataType - } - }, '*'); - } + func: postMessageWithDataTypeToEveryone }); }; const statesKey = `${appName}_${STATES_KEY}`; - const states = (await chrome.storage.sync.get(statesKey))[statesKey] as Record | undefined; + const { [statesKey]: states } = (await chrome.storage.sync.get | undefined>>(statesKey)); const activateStateNameKey = `${appName}_${ACTIVE_STATE_NAME_KEY}`; - const activateStateName = (await chrome.storage.sync.get(activateStateNameKey))[activateStateNameKey] as string | undefined; - const activeState = states?.[activateStateName || '']; + const { [activateStateNameKey]: activateStateName = '' } = (await chrome.storage.sync.get>(activateStateNameKey)); + const activeState = states?.[activateStateName]; if (activeState) { activeStateAppliedOn.add(tabId); + Object.entries(activeState?.configurations || {}).forEach(([id, configValue]) => sendMessage('updateConfig', { id, configValue })); Object.entries(activeState?.localizations || {}).forEach(([lang, overrides]) => { Object.entries(overrides).forEach(([key, value]) => sendMessage('updateLocalization', { key, value, lang })); }); - if (Object.keys(activeState.stylingVariables || {}).length) { + if (Object.keys(activeState.stylingVariables || {}).length > 0) { void sendMessage('updateStylingVariables', { variables: activeState.stylingVariables }); @@ -111,12 +128,22 @@ const applyActivateState = async (appName: string, tabId: number) => { /** map of connection base on Tab ID */ const connections = new Map(); +function postMessageToEveryone(content: string, type: typeof postMessageType) { + window.postMessage({ + type: type, + to: 'app', + + content: JSON.parse(content) + }, '*'); +} + chrome.runtime.onConnect.addListener((port) => { let tabId: number | undefined; // assign the listener function to a variable so we can remove it later const devToolsListener = async (message: any) => { // reject all messages not coming from the devtools if (!isOtterDebugMessage(message) || !isExtensionMessage(message)) { + // eslint-disable-next-line no-console -- Needed to warn the user return console.warn('Unknown message', message); } @@ -140,14 +167,7 @@ chrome.runtime.onConnect.addListener((port) => { await chrome.scripting.executeScript({ target: { tabId }, args: [JSON.stringify(content), postMessageType], - // eslint-disable-next-line prefer-arrow/prefer-arrow-functions, @typescript-eslint/no-shadow - func: function (content, postMessageType) { - window.postMessage({ - type: postMessageType, - to: 'app', - content: JSON.parse(content) - }, '*'); - } + func: postMessageToEveryone }); } }; @@ -159,8 +179,8 @@ chrome.runtime.onConnect.addListener((port) => { if (tabId) { const ports = connections.get(tabId); if (ports) { - const newPorts = ports.filter(p => p !== port); - if (newPorts.length) { + const newPorts = ports.filter((p) => p !== port); + if (newPorts.length > 0) { connections.set(tabId, newPorts); } else { connections.delete(tabId); diff --git a/apps/chrome-devtools/src/extension/interface.ts b/apps/chrome-devtools/src/extension/interface.ts index 7426c87b39..2d59f70914 100644 --- a/apps/chrome-devtools/src/extension/interface.ts +++ b/apps/chrome-devtools/src/extension/interface.ts @@ -1,5 +1,11 @@ -import type { OtterMessage, OtterMessageContent } from '@o3r/core'; -import type { AvailableMessageContents } from '../services/message.interface'; +import type { + Configuration, + OtterMessage, + OtterMessageContent, +} from '@o3r/core'; +import type { + AvailableMessageContents, +} from '../shared/index'; /** Message from the DevTools message */ export interface ExtensionMessage extends OtterMessage { @@ -12,7 +18,7 @@ export interface StateOverride { * Key is the configuration identifier * Value is the new configuration value */ - configurations?: Record; + configurations?: Record; /** * First key is the language * Second key is the localization key diff --git a/apps/chrome-devtools/src/extension/options.ts b/apps/chrome-devtools/src/extension/options.ts index 08b7c8ab1b..6fa6b171a8 100644 --- a/apps/chrome-devtools/src/extension/options.ts +++ b/apps/chrome-devtools/src/extension/options.ts @@ -1,16 +1,18 @@ -import type { WHITELISTED_HOSTS_KEY as Key } from './interface'; +import type { + WHITELISTED_HOSTS_KEY as Key, +} from './interface'; const WHITELISTED_HOSTS_KEY: typeof Key = 'WHITELISTED_HOSTS'; const saveOptions = () => { - const whitelistHosts = (document.getElementById('hosts') as HTMLTextAreaElement).value.split('\n'); + const whitelistHosts = (document.querySelector('#hosts') as HTMLTextAreaElement).value.split('\n'); return chrome.storage.sync.set({ [WHITELISTED_HOSTS_KEY]: whitelistHosts }); }; const restoreOptions = async () => { - const whitelistHosts = (await chrome.storage.sync.get(WHITELISTED_HOSTS_KEY))[WHITELISTED_HOSTS_KEY] as string[] || ['localhost']; - (document.getElementById('hosts') as HTMLTextAreaElement).value = whitelistHosts.join('\n'); + const { [WHITELISTED_HOSTS_KEY]: whitelistHosts = ['localhost'] } = (await chrome.storage.sync.get>(WHITELISTED_HOSTS_KEY)); + (document.querySelector('#hosts') as HTMLTextAreaElement).value = whitelistHosts.join('\n'); }; document.addEventListener('DOMContentLoaded', restoreOptions); -document.getElementById('save')!.addEventListener('click', saveOptions); +document.querySelector('#save')!.addEventListener('click', saveOptions); diff --git a/apps/chrome-devtools/src/extension/wrap.ts b/apps/chrome-devtools/src/extension/wrap.ts index fff49bda9f..d25debad70 100644 --- a/apps/chrome-devtools/src/extension/wrap.ts +++ b/apps/chrome-devtools/src/extension/wrap.ts @@ -1,4 +1,9 @@ -import type { applicationMessageTarget, ConnectContentMessage, OtterMessage } from '@o3r/core'; +/* eslint-disable no-console -- this file will be injected in the app and cannot have dependencies */ +import type { + applicationMessageTarget, + ConnectContentMessage, + OtterMessage, +} from '@o3r/core'; /* This script is injected into the page by the Otter Devtools extension. It listens for messages from the application and sends them to the background service. @@ -13,17 +18,20 @@ window.postMessage({ } } as OtterMessage, '*'); +declare namespace globalThis { + let localMessageListener: ((this: Window, ev: MessageEvent) => any) | undefined; +} + // Remove previous listener (if the script is reloaded by a previous extension instance) -if ((globalThis as any).localMessageListener) { - window.removeEventListener('message', (globalThis as any).localMessageListener); +if (globalThis.localMessageListener) { + window.removeEventListener('message', globalThis.localMessageListener); } /** * Listener for messages from the page * @param event */ -// eslint-disable-next-line prefer-arrow/prefer-arrow-functions -(globalThis as any).localMessageListener = function (event: MessageEvent) { +function messageListener(event: MessageEvent) { let message: any; try { message = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; @@ -36,10 +44,14 @@ if ((globalThis as any).localMessageListener) { void chrome.runtime.sendMessage(message); } catch (e) { console.warn('Chrome devtool could not send message', e); - window.removeEventListener('message', (globalThis as any).localMessageListener); + if (globalThis.localMessageListener) { + window.removeEventListener('message', globalThis.localMessageListener); + } } } -}; +} + +globalThis.localMessageListener = messageListener; // Listen for messages from the application -window.addEventListener('message', (globalThis as any).localMessageListener); +window.addEventListener('message', globalThis.localMessageListener); diff --git a/apps/chrome-devtools/src/services/connection.service.ts b/apps/chrome-devtools/src/services/connection.service.ts index 1902be73b8..50db578ab3 100644 --- a/apps/chrome-devtools/src/services/connection.service.ts +++ b/apps/chrome-devtools/src/services/connection.service.ts @@ -1,8 +1,34 @@ -import { ApplicationRef, Injectable, OnDestroy, signal } from '@angular/core'; -import type { Dictionary } from '@ngrx/entity'; -import type { ConfigurationModel } from '@o3r/configuration'; -import { otterMessageType } from '@o3r/core'; -import {type Observable, of, ReplaySubject, Subscription} from 'rxjs'; +import { + ApplicationRef, + Injectable, + OnDestroy, + signal, +} from '@angular/core'; +import type { + Dictionary, +} from '@ngrx/entity'; +import type { + ApplicationInformationContentMessage, +} from '@o3r/application'; +import type { + SelectedComponentInfoMessage, +} from '@o3r/components'; +import type { + ConfigurationModel, + ConfigurationsMessage, +} from '@o3r/configuration'; +import { + otterMessageType, +} from '@o3r/core'; +import type { + RulesEngineDebugEventsContentMessage, +} from '@o3r/rules-engine'; +import { + type Observable, + of, + ReplaySubject, + Subscription, +} from 'rxjs'; import { catchError, debounceTime, @@ -12,19 +38,12 @@ import { shareReplay, startWith, take, - timeout + timeout, } from 'rxjs/operators'; -import type { AvailableMessageContents } from './message.interface'; - -import type { ApplicationInformationContentMessage } from '@o3r/application'; -import type { SelectedComponentInfoMessage } from '@o3r/components'; -import type { ConfigurationsMessage } from '@o3r/configuration'; -import type { RulesEngineDebugEventsContentMessage } from '@o3r/rules-engine'; - -/** - * Path to the script that is injected into the page. - */ -export const scriptToInject = 'extension/wrap.js'; +import { + type AvailableMessageContents, + scriptToInject, +} from '../shared/index'; /** * Determine if the message is an ApplicationInformationContentMessage @@ -63,11 +82,11 @@ export const filterAndMapMessage = ( filterFn: (message: AvailableMessageContents) => message is T, mapFn: (message: T) => R ) => (message$: Observable) => message$.pipe( - filter(filterFn), - map(mapFn), - distinctUntilChanged(), - shareReplay({ refCount: true, bufferSize: 1 }) - ); + filter(filterFn), + map(mapFn), + distinctUntilChanged(), + shareReplay({ refCount: true, bufferSize: 1 }) +); export type AppState = 'loading' | 'timeout' | 'connected'; @@ -76,7 +95,6 @@ export type AppState = 'loading' | 'timeout' | 'connected'; */ @Injectable({ providedIn: 'root' }) export class ChromeExtensionConnectionService implements OnDestroy { - private backgroundPageConnection?: chrome.runtime.Port; private readonly messageSubject = new ReplaySubject(1); private readonly subscription = new Subscription(); @@ -93,7 +111,6 @@ export class ChromeExtensionConnectionService implements OnDestroy { catchError(() => of('timeout' as AppState)) ); - private readonly configurations = new ReplaySubject>(1); public configurations$ = this.configurations.asObservable(); @@ -106,14 +123,13 @@ export class ChromeExtensionConnectionService implements OnDestroy { public activate() { this.isDisconnected.set(false); this.backgroundPageConnection = chrome.runtime.connect(); - // eslint-disable-next-line @typescript-eslint/require-await - this.backgroundPageConnection.onMessage.addListener(async (message) => this.messageSubject.next(message.content)); + this.backgroundPageConnection.onMessage.addListener((message: { content: AvailableMessageContents }) => this.messageSubject.next(message.content)); this.backgroundPageConnection.onDisconnect.addListener(() => { this.isDisconnected.set(true); }); - this.sendMessage('inject', {scriptToInject}); + this.sendMessage('inject', { scriptToInject }); } /** diff --git a/apps/chrome-devtools/src/services/index.ts b/apps/chrome-devtools/src/services/index.ts index 7f24577b8c..f7c29224ab 100644 --- a/apps/chrome-devtools/src/services/index.ts +++ b/apps/chrome-devtools/src/services/index.ts @@ -1,5 +1,3 @@ export * from './connection.service'; export * from './localization.service'; -export * from './message.interface'; export * from './state.service'; - diff --git a/apps/chrome-devtools/src/services/localization.service.ts b/apps/chrome-devtools/src/services/localization.service.ts index f0b5b2fd74..618a41f10b 100644 --- a/apps/chrome-devtools/src/services/localization.service.ts +++ b/apps/chrome-devtools/src/services/localization.service.ts @@ -1,14 +1,31 @@ -import { inject, Injectable, signal, type Signal } from '@angular/core'; -import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop'; +import { + inject, + Injectable, + signal, + type Signal, +} from '@angular/core'; +import { + takeUntilDestroyed, + toObservable, + toSignal, +} from '@angular/core/rxjs-interop'; import type { GetTranslationValuesContentMessage, IsTranslationDeactivationEnabledContentMessage, LanguagesContentMessage, LocalizationsContentMessage, - SwitchLanguageContentMessage + SwitchLanguageContentMessage, } from '@o3r/localization'; -import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; -import { ChromeExtensionConnectionService, filterAndMapMessage } from './connection.service'; +import { + distinctUntilChanged, + filter, + map, + switchMap, +} from 'rxjs/operators'; +import { + ChromeExtensionConnectionService, + filterAndMapMessage, +} from './connection.service'; @Injectable({ providedIn: 'root' }) export class LocalizationService { @@ -23,12 +40,14 @@ export class LocalizationService { ), { initialValue: [] } ); + public readonly languages$ = this.connectionService.message$.pipe( filterAndMapMessage( (message): message is LanguagesContentMessage => message.dataType === 'languages', (message) => message.languages ) ); + public readonly languages = toSignal(this.languages$, { initialValue: [] }); public readonly isTranslationDeactivationEnabled = toSignal( this.connectionService.message$.pipe( @@ -39,6 +58,7 @@ export class LocalizationService { ), { initialValue: false } ); + public readonly currentLanguage = this.lang.asReadonly(); public readonly translationsForCurrentLanguage: Signal> = toSignal( diff --git a/apps/chrome-devtools/src/services/message.interface.ts b/apps/chrome-devtools/src/services/message.interface.ts deleted file mode 100644 index 642b15e280..0000000000 --- a/apps/chrome-devtools/src/services/message.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { AvailableApplicationMessageContents } from '@o3r/application'; -import type { AvailableComponentsMessageContents } from '@o3r/components'; -import type { AvailableConfigurationMessageContents } from '@o3r/configuration'; -import type { CommonContentMessages } from '@o3r/core'; -import type { AvailableLocalizationMessageContents } from '@o3r/localization'; -import type { AvailableRulesEngineMessageContents } from '@o3r/rules-engine'; -import type { AvailableStylingMessageContents } from '@o3r/styling'; - -export type AvailableMessageContents = - | AvailableComponentsMessageContents - | AvailableConfigurationMessageContents - | AvailableLocalizationMessageContents - | AvailableRulesEngineMessageContents - | AvailableApplicationMessageContents - | AvailableStylingMessageContents - | CommonContentMessages; - diff --git a/apps/chrome-devtools/src/services/ruleset-history.service.ts b/apps/chrome-devtools/src/services/ruleset-history.service.ts index b523930fc9..cc010cb513 100644 --- a/apps/chrome-devtools/src/services/ruleset-history.service.ts +++ b/apps/chrome-devtools/src/services/ruleset-history.service.ts @@ -1,10 +1,31 @@ -import { effect, inject, Injectable } from '@angular/core'; -import type { RulesEngineDebugEventsContentMessage, RulesetExecutionDebug } from '@o3r/rules-engine'; -import { rulesetReportToHistory } from '@o3r/rules-engine'; -import { filter, map, Observable, ReplaySubject } from 'rxjs'; -import { shareReplay } from 'rxjs/operators'; -import { ChromeExtensionConnectionService, isRuleEngineEventsMessage } from './connection.service'; -import { toSignal } from '@angular/core/rxjs-interop'; +import { + effect, + inject, + Injectable, +} from '@angular/core'; +import { + toSignal, +} from '@angular/core/rxjs-interop'; +import type { + RulesEngineDebugEventsContentMessage, + RulesetExecutionDebug, +} from '@o3r/rules-engine'; +import { + rulesetReportToHistory, +} from '@o3r/rules-engine'; +import { + filter, + map, + Observable, + ReplaySubject, +} from 'rxjs'; +import { + shareReplay, +} from 'rxjs/operators'; +import { + ChromeExtensionConnectionService, + isRuleEngineEventsMessage, +} from './connection.service'; @Injectable({ providedIn: 'root' @@ -20,8 +41,8 @@ export class RulesetHistoryService { * History of ruleset executed */ public readonly rulesetExecutions$: Observable = this.ruleEngineDebugEvents$.pipe( - map(({events, rulesetMap}) => rulesetReportToHistory(events, rulesetMap)), - shareReplay({bufferSize: 1, refCount: true}) + map(({ events, rulesetMap }) => rulesetReportToHistory(events, rulesetMap)), + shareReplay({ bufferSize: 1, refCount: true }) ); constructor() { diff --git a/apps/chrome-devtools/src/services/state.service.ts b/apps/chrome-devtools/src/services/state.service.ts index b2c61cb5c2..3f6429c675 100644 --- a/apps/chrome-devtools/src/services/state.service.ts +++ b/apps/chrome-devtools/src/services/state.service.ts @@ -1,15 +1,35 @@ -import { computed, effect, inject, Injectable, type Signal, signal } from '@angular/core'; -import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop'; -import { combineLatest } from 'rxjs'; +import { + computed, + effect, + inject, + Injectable, + type Signal, + signal, +} from '@angular/core'; +import { + takeUntilDestroyed, + toObservable, + toSignal, +} from '@angular/core/rxjs-interop'; +import { + combineLatest, +} from 'rxjs'; +import { + ACTIVE_STATE_NAME_KEY, + type State, + type StateOverride, + STATES_KEY, +} from '../extension/interface'; import { ChromeExtensionConnectionService, filterAndMapMessage, - isApplicationInformationMessage + isApplicationInformationMessage, } from './connection.service'; -import { ACTIVE_STATE_NAME_KEY, type State, type StateOverride, STATES_KEY } from '../extension/interface'; -import { LocalizationService } from './localization.service'; +import { + LocalizationService, +} from './localization.service'; -@Injectable({providedIn: 'root'}) +@Injectable({ providedIn: 'root' }) export class StateService { private readonly connectionService = inject(ChromeExtensionConnectionService); private readonly localizationService = inject(LocalizationService); @@ -37,7 +57,7 @@ export class StateService { effect(async () => { const key = this.statesStorageKey(); if (key) { - const states = (await chrome.storage.sync.get(key))[key] as Record | undefined; + const { [key]: states } = (await chrome.storage.sync.get | undefined>>(key)); if (states) { this.states.set(states); } @@ -57,7 +77,7 @@ export class StateService { effect(async () => { const key = this.activeStateNameStorageKey(); if (key) { - const name = (await chrome.storage.sync.get(key))[key] as string | undefined; + const { [key]: name } = (await chrome.storage.sync.get>(key)); if (name) { this.activeStateName.set(name); } @@ -88,7 +108,7 @@ export class StateService { // TODO reset configuration (is it possible? based on default value from metadata if present?) // Reset all styling variables before applying override of the new state this.connectionService.sendMessage('resetStylingVariables', {}); - languages.forEach((lang) => this.connectionService.sendMessage('reloadLocalizationKeys', {lang})); + languages.forEach((lang) => this.connectionService.sendMessage('reloadLocalizationKeys', { lang })); if (!state) { this.connectionService.sendMessage('unselectState', {}); return; @@ -103,7 +123,7 @@ export class StateService { }); }); }); - if (state.stylingVariables && Object.keys(state.stylingVariables).length) { + if (state.stylingVariables && Object.keys(state.stylingVariables).length > 0) { this.connectionService.sendMessage('updateStylingVariables', { variables: state.stylingVariables }); @@ -142,7 +162,6 @@ export class StateService { } public updateState(oldStateName: string, state: State) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars this.states.update(({ [oldStateName]: oldState, ...remainingStates }) => ({ ...remainingStates, [state.name]: state @@ -150,7 +169,6 @@ export class StateService { } public deleteState(stateName: string) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars this.states.update(({ [stateName]: state, ...remainingStates }) => remainingStates); } @@ -190,9 +208,9 @@ export class StateService { ...changes.stylingVariables }).filter((entry): entry is [string, string] => entry[1] !== null)); const stateOverrides: StateOverride = { - configurations: Object.keys(configurationOverrides).length ? configurationOverrides : undefined, - localizations: Object.keys(localizationOverrides || {}).length ? localizationOverrides : undefined, - stylingVariables: Object.keys(stylingOverrides).length ? stylingOverrides : undefined + configurations: Object.keys(configurationOverrides).length > 0 ? configurationOverrides : undefined, + localizations: Object.keys(localizationOverrides || {}).length > 0 ? localizationOverrides : undefined, + stylingVariables: Object.keys(stylingOverrides).length > 0 ? stylingOverrides : undefined }; return { ...state, diff --git a/apps/chrome-devtools/src/shared/index.ts b/apps/chrome-devtools/src/shared/index.ts new file mode 100644 index 0000000000..4198830e49 --- /dev/null +++ b/apps/chrome-devtools/src/shared/index.ts @@ -0,0 +1,6 @@ +export * from './message.interface'; + +/** + * Path to the script that is injected into the page. + */ +export const scriptToInject = 'extension/wrap.js'; diff --git a/apps/chrome-devtools/src/shared/message.interface.ts b/apps/chrome-devtools/src/shared/message.interface.ts new file mode 100644 index 0000000000..b9718c1fd7 --- /dev/null +++ b/apps/chrome-devtools/src/shared/message.interface.ts @@ -0,0 +1,30 @@ +import type { + AvailableApplicationMessageContents, +} from '@o3r/application'; +import type { + AvailableComponentsMessageContents, +} from '@o3r/components'; +import type { + AvailableConfigurationMessageContents, +} from '@o3r/configuration'; +import type { + CommonContentMessages, +} from '@o3r/core'; +import type { + AvailableLocalizationMessageContents, +} from '@o3r/localization'; +import type { + AvailableRulesEngineMessageContents, +} from '@o3r/rules-engine'; +import type { + AvailableStylingMessageContents, +} from '@o3r/styling'; + +export type AvailableMessageContents = + | AvailableComponentsMessageContents + | AvailableConfigurationMessageContents + | AvailableLocalizationMessageContents + | AvailableRulesEngineMessageContents + | AvailableApplicationMessageContents + | AvailableStylingMessageContents + | CommonContentMessages; diff --git a/apps/chrome-devtools/testing/jest.config.ut.js b/apps/chrome-devtools/testing/jest.config.ut.js index ca5548dfff..4f26a9eae8 100644 --- a/apps/chrome-devtools/testing/jest.config.ut.js +++ b/apps/chrome-devtools/testing/jest.config.ut.js @@ -1,5 +1,6 @@ const path = require('node:path'); const getJestProjectConfig = require('../../../jest.config.ut').getJestProjectConfig; + const rootDir = path.join(__dirname, '..'); const baseConfig = getJestProjectConfig(rootDir, true); diff --git a/apps/chrome-devtools/testing/setup-jest.ts b/apps/chrome-devtools/testing/setup-jest.ts index ab68e1eb87..1100b3e8a6 100644 --- a/apps/chrome-devtools/testing/setup-jest.ts +++ b/apps/chrome-devtools/testing/setup-jest.ts @@ -1,2 +1 @@ import 'jest-preset-angular/setup-jest'; - diff --git a/apps/chrome-devtools/tsconfig.build.components.json b/apps/chrome-devtools/tsconfig.build.components.json index 2c6a7ecde4..d2c66d67af 100644 --- a/apps/chrome-devtools/tsconfig.build.components.json +++ b/apps/chrome-devtools/tsconfig.build.components.json @@ -1,5 +1,13 @@ { "extends": "./tsconfig.build", + "compilerOptions": { + "composite": false + }, "include": ["src/**/*.ts"], - "exclude": ["**/*.spec.ts", "src/app-devtools*.ts", "src/app-devtools/**/*.ts", "src/extension/**/*.ts"] + "exclude": [ + "**/*.spec.ts", + "src/app-devtools*.ts", + "src/app-devtools/**/*.ts", + "src/extension/**/*.ts" + ] } diff --git a/apps/chrome-devtools/tsconfig.build.devtools.json b/apps/chrome-devtools/tsconfig.build.devtools.json index 58965d0b5d..4fe569558e 100644 --- a/apps/chrome-devtools/tsconfig.build.devtools.json +++ b/apps/chrome-devtools/tsconfig.build.devtools.json @@ -1,8 +1,14 @@ { "extends": "./tsconfig.build", "compilerOptions": { - "tsBuildInfoFile": "build/devtools.tsbuildinfo" + "tsBuildInfoFile": "build/devtools.tsbuildinfo", + "composite": false }, "include": ["src/**/*.ts"], - "exclude": ["**/*.spec.ts", "src/app-components*.ts", "src/app-components/**/*.ts", "src/extension/**/*.ts"] + "exclude": [ + "**/*.spec.ts", + "src/app-components*.ts", + "src/app-components/**/*.ts", + "src/extension/**/*.ts" + ] } diff --git a/apps/chrome-devtools/tsconfig.build.json b/apps/chrome-devtools/tsconfig.build.json index b666a68430..b91e6a91fb 100644 --- a/apps/chrome-devtools/tsconfig.build.json +++ b/apps/chrome-devtools/tsconfig.build.json @@ -1,15 +1,23 @@ { "extends": "../../tsconfig.build", "compilerOptions": { - "lib": ["es2021", "dom"], + "lib": ["es2022", "dom"], "types": ["chrome"], "outDir": "dist/", - "module": "es2020", + "module": "es2022", "moduleResolution": "node", "incremental": true, + "composite": true, "tsBuildInfoFile": "build/.tsbuildinfo" }, "angularCompilerOptions": { "compilationMode": "full" - } + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts", + "src/extension/**/*.ts" + ] } diff --git a/apps/chrome-devtools/tsconfig.eslint.json b/apps/chrome-devtools/tsconfig.eslint.json index 15a57e6278..6c7da70387 100644 --- a/apps/chrome-devtools/tsconfig.eslint.json +++ b/apps/chrome-devtools/tsconfig.eslint.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.build", "include": [ - ".eslintrc.js", + "eslint*.config.mjs", "jest.config.js", "testing/setup-jest.ts", "scripts/**/*.cjs" diff --git a/apps/chrome-devtools/tsconfig.extension.json b/apps/chrome-devtools/tsconfig.extension.json index dd750dd447..85766b6c9a 100644 --- a/apps/chrome-devtools/tsconfig.extension.json +++ b/apps/chrome-devtools/tsconfig.extension.json @@ -8,9 +8,14 @@ "outDir": "dist", "module": "ES2020", "moduleResolution": "Node", - "tsBuildInfoFile": "build/extension.tsbuildinfo" + "tsBuildInfoFile": "build/extension.tsbuildinfo", + "composite": true }, "include": [ - "src/extension/**/*.ts" + "src/extension/**/*.ts", + "src/shared/**/*.ts", + ], + "exclude": [ + "**/*.spec.ts" ] } diff --git a/apps/chrome-devtools/tsconfig.json b/apps/chrome-devtools/tsconfig.json index f49d221998..988c2b1138 100644 --- a/apps/chrome-devtools/tsconfig.json +++ b/apps/chrome-devtools/tsconfig.json @@ -3,10 +3,7 @@ "extends": "../../tsconfig.base", "references": [ { - "path": "./tsconfig.build.components.json" - }, - { - "path": "./tsconfig.build.devtools.json" + "path": "./tsconfig.build.json" }, { "path": "./tsconfig.extension.json" diff --git a/apps/github-cascading-app/.eslintrc.js b/apps/github-cascading-app/.eslintrc.js deleted file mode 100644 index dc912ec5d8..0000000000 --- a/apps/github-cascading-app/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable quote-props */ - -module.exports = { - 'root': true, - 'parserOptions': { - 'tsconfigRootDir': __dirname, - 'project': [ - 'tsconfig.build.json', - 'tsconfig.spec.json', - 'tsconfig.eslint.json' - ], - 'sourceType': 'module' - }, - 'extends': [ - '../../.eslintrc.js' - ] -}; diff --git a/apps/github-cascading-app/eslint.config.mjs b/apps/github-cascading-app/eslint.config.mjs new file mode 100644 index 0000000000..d86dd85d02 --- /dev/null +++ b/apps/github-cascading-app/eslint.config.mjs @@ -0,0 +1,7 @@ +import shared from '../../eslint.shared.config.mjs'; +import local from './eslint.local.config.mjs'; + +export default [ + ...shared, + ...local +]; diff --git a/apps/github-cascading-app/eslint.local.config.mjs b/apps/github-cascading-app/eslint.local.config.mjs new file mode 100644 index 0000000000..783a3aa3b3 --- /dev/null +++ b/apps/github-cascading-app/eslint.local.config.mjs @@ -0,0 +1,38 @@ +import { + dirname, +} from 'node:path'; +import { + fileURLToPath, +} from 'node:url'; +import globals from 'globals'; + +const __filename = fileURLToPath(import.meta.url); +// __dirname is not defined in ES module scope +const __dirname = dirname(__filename); + +export default [ + { + name: '@o3r/github-cascading-app/projects', + languageOptions: { + sourceType: 'module', + parserOptions: { + tsconfigRootDir: __dirname, + project: [ + 'tsconfig.build.json', + 'tsconfig.spec.json', + 'tsconfig.eslint.json' + ] + } + } + }, + { + name: '@o3r/github-cascading-app/globals', + languageOptions: { + globals: { + ...globals.node, + NodeJS: true, + globalThis: true + } + } + } +]; diff --git a/apps/github-cascading-app/package.json b/apps/github-cascading-app/package.json index 8b95a7038a..5f912e33cd 100644 --- a/apps/github-cascading-app/package.json +++ b/apps/github-cascading-app/package.json @@ -23,9 +23,6 @@ }, "devDependencies": { "@actions/github": "^6.0.0", - "@angular-eslint/builder": "~18.3.0", - "@angular-eslint/eslint-plugin": "~18.3.0", - "@angular-eslint/eslint-plugin-template": "~18.3.0", "@angular/animations": "~18.2.0", "@angular/common": "~18.2.0", "@angular/compiler": "~18.2.0", @@ -33,29 +30,34 @@ "@angular/core": "~18.2.0", "@angular/platform-browser": "~18.2.0", "@angular/platform-browser-dynamic": "~18.2.0", + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0", "@nx/eslint": "~19.5.0", "@nx/eslint-plugin": "~19.5.0", "@nx/jest": "~19.5.0", "@nx/js": "~19.5.0", - "@o3r/eslint-config-otter": "workspace:^", + "@o3r/eslint-config": "workspace:^", "@o3r/eslint-plugin": "workspace:^", - "@stylistic/eslint-plugin-ts": "~2.4.0", + "@stylistic/eslint-plugin": "~2.7.0", "@types/ejs": "^3.1.2", "@types/express-serve-static-core": "^4.19.5", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", "@types/semver": "^7.3.13", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.14.1", - "@typescript-eslint/utils": "^7.14.1", + "@typescript-eslint/parser": "~8.12.2", + "angular-eslint": "~18.4.0", "cpy-cli": "^5.0.0", "ejs": "^3.1.9", - "eslint": "^8.57.0", + "eslint": "~9.14.0", "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import-newlines": "^1.4.0", "eslint-plugin-jest": "~28.8.0", - "eslint-plugin-jsdoc": "~48.11.0", + "eslint-plugin-jsdoc": "~50.2.0", "eslint-plugin-prefer-arrow": "~1.2.3", - "eslint-plugin-unicorn": "^54.0.0", + "eslint-plugin-unicorn": "^56.0.0", + "eslint-plugin-unused-imports": "^4.1.4", + "globals": "^15.9.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", "jest-junit": "~16.0.0", @@ -67,6 +69,7 @@ "ts-jest": "~29.2.0", "tslib": "^2.6.2", "typescript": "~5.5.4", + "typescript-eslint": "~8.12.2", "zone.js": "~0.14.2" }, "engines": { diff --git a/apps/github-cascading-app/project.json b/apps/github-cascading-app/project.json index 66b0a51f2e..533414f485 100644 --- a/apps/github-cascading-app/project.json +++ b/apps/github-cascading-app/project.json @@ -16,13 +16,7 @@ } }, "lint": { - "options": { - "eslintConfig": "apps/github-cascading-app/.eslintrc.js", - "lintFilePatterns": [ - "apps/github-cascading-app/src/**/*.ts", - "apps/github-cascading-app/package.json" - ] - } + "executor": "nx:run-commands" }, "test": { "executor": "@nx/jest:jest", diff --git a/apps/github-cascading-app/scripts/prepare-azure-function.mjs b/apps/github-cascading-app/scripts/prepare-azure-function.mjs index fbdd4f64a9..422b69a63c 100644 --- a/apps/github-cascading-app/scripts/prepare-azure-function.mjs +++ b/apps/github-cascading-app/scripts/prepare-azure-function.mjs @@ -1,11 +1,18 @@ -import { promises as fs } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { + promises as fs, +} from 'node:fs'; +import { + dirname, + resolve, +} from 'node:path'; +import { + fileURLToPath, +} from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); void (async () => { - const pck = JSON.parse(await fs.readFile(resolve(__dirname, '..', 'package.json'), { encoding: 'utf-8' })); + const pck = JSON.parse(await fs.readFile(resolve(__dirname, '..', 'package.json'), { encoding: 'utf8' })); delete pck.devDependencies; delete pck.scripts; await fs.writeFile(resolve(__dirname, '..', 'dist', 'package.json'), JSON.stringify(pck, null, 2)); diff --git a/apps/github-cascading-app/src/app.ts b/apps/github-cascading-app/src/app.ts index 4ad3532c27..6080a4700b 100644 --- a/apps/github-cascading-app/src/app.ts +++ b/apps/github-cascading-app/src/app.ts @@ -1,8 +1,11 @@ -import type { Probot } from 'probot'; -import { CascadingProbot } from './cascading/cascading-probot'; +import type { + Probot, +} from 'probot'; +import { + CascadingProbot, +} from './cascading/cascading-probot'; export = (app: Probot) => { - app.on(['push'], async (context) => { const logger = context.log; const branch = context.payload.ref.replace(/^refs\/heads\//, ''); @@ -55,18 +58,18 @@ export = (app: Probot) => { logger.debug(`The check suite ${context.payload.check_suite.id} is not passed yet`); return; } - if (!['neutral', 'success'].some((status) => context.payload.check_suite.conclusion === status)) { + if (!context.payload.check_suite.conclusion || !['neutral', 'success'].includes(context.payload.check_suite.conclusion)) { logger.debug(`The check suite ${context.payload.check_suite.id} is not passed yet`); return; } await Promise.all( context.payload.check_suite.pull_requests - .map(async ({id, head}) => { + .map(async ({ id, head }) => { try { const branch = head.ref.replace(/^refs\/heads\//, ''); await cascadingPlugin.mergeCascadingPullRequest({ id }, branch, context.payload.check_suite.conclusion); } catch (error: any) { - context.octokit.log.error(`Caught an error during the merge execution on PR ${id}`, error); + context.octokit.log.error(`Caught an error during the merge execution on PR ${id}\n${error instanceof Error ? error.stack : error}`); } }) ); diff --git a/apps/github-cascading-app/src/cascading/cascading-probot.ts b/apps/github-cascading-app/src/cascading/cascading-probot.ts index 88d9601d28..1051971c87 100644 --- a/apps/github-cascading-app/src/cascading/cascading-probot.ts +++ b/apps/github-cascading-app/src/cascading/cascading-probot.ts @@ -1,6 +1,16 @@ -import { ProbotOctokit } from 'probot'; -import { BaseLogger, CascadingConfiguration, CascadingPullRequestInfo, CheckConclusion, DEFAULT_CONFIGURATION } from './interfaces'; -import { Cascading } from './cascading'; +import { + ProbotOctokit, +} from 'probot'; +import { + Cascading, +} from './cascading'; +import { + BaseLogger, + CascadingConfiguration, + CascadingPullRequestInfo, + CheckConclusion, + DEFAULT_CONFIGURATION, +} from './interfaces'; export interface CascadingProbotOptions { /** Logger to report information */ @@ -17,7 +27,6 @@ export interface CascadingProbotOptions { * Cascading class implementation for Probot framework */ export class CascadingProbot extends Cascading { - /** List of possible configuration files paths */ public static readonly CONFIGURATION_FILES = ['.github/cascadingrc.json', '.github/.cascadingrc.json', 'cascadingrc.json', '.cascadingrc.json']; @@ -38,19 +47,17 @@ export class CascadingProbot extends Cascading { }).catch(() => null))); const configFileValidResponses = configFileResponses - .map((res) => res && !Array.isArray(res.data) && res.data || null) + .map((res) => (res && !Array.isArray(res.data) && res.data) || null) .filter((res) => !!res); const configFileResponse = configFileValidResponses[0]; if (configFileValidResponses.length > 1) { this.logger.warn(`Several configuration files have been found (${configFileValidResponses.map((c) => c?.path).join(', ')}). The files will be ignored.`); - } else if (!configFileResponse) { - this.logger.warn('No remote Configuration found, the default configuration will be used'); - } else { + } else if (configFileResponse) { try { - const configFileResponseWithContent: { content?: string; encoding?: 'utf8' | 'utf-8' | 'base64' } = configFileResponse as unknown as any; - const parsedConfig = configFileResponseWithContent.content && JSON.parse(Buffer.from(configFileResponseWithContent.content, configFileResponseWithContent.encoding || 'base64').toString()); + const configFileResponseWithContent = configFileResponse as { content?: string; encoding?: 'utf8' | 'base64' }; + const parsedConfig = JSON.parse(Buffer.from(configFileResponseWithContent.content || '{}', configFileResponseWithContent.encoding || 'base64').toString()) as Partial; if (parsedConfig) { this.logger.debug(`Found configuration on ${configFileResponse.url}`); remoteConfig = parsedConfig; @@ -62,6 +69,8 @@ export class CascadingProbot extends Cascading { this.logger.warn('Failed to parse the configuration, the default configuration will be used'); this.logger.warn(JSON.stringify(error, null, 2)); } + } else { + this.logger.warn('No remote Configuration found, the default configuration will be used'); } const config = { @@ -71,7 +80,8 @@ export class CascadingProbot extends Cascading { if (!config.defaultBranch) { this.logger.debug('No default branch, will be retrieve from Github'); - config.defaultBranch = (await this.options.octokit.repos.get(this.options.repo)).data.default_branch; + const { data } = await this.options.octokit.repos.get(this.options.repo); + config.defaultBranch = data.default_branch; } this.logger.debug('Configuration'); @@ -81,11 +91,15 @@ export class CascadingProbot extends Cascading { /** @inheritdoc */ protected async isCascadingPullRequest(id: string | number) { - return this.options.appId !== undefined && (await this.options.octokit.pulls.get({ - ...this.options.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase - pull_number: +id - })).data.user?.id === +this.options.appId; + if (this.options.appId !== undefined) { + const { data } = await this.options.octokit.pulls.get({ + ...this.options.repo, + + pull_number: +id + }); + return data.user?.id === +this.options.appId; + } + return false; } /** @inheritdoc */ @@ -96,11 +110,12 @@ export class CascadingProbot extends Cascading { /** @inheritdoc */ protected async mergePullRequest(id: string | number) { - return (await this.options.octokit.pulls.merge({ + const { data } = await this.options.octokit.pulls.merge({ ...this.options.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + pull_number: +id - })).data.merged; + }); + return data.merged; } /** @inheritdoc */ @@ -111,48 +126,47 @@ export class CascadingProbot extends Cascading { this.options.octokit.repos.getBranch({ ...this.options.repo, branch: `refs/heads/${branch}` - }).then(({data}) => data.commit.sha) + }).then((res) => res.data.commit.sha) ) ); - /* eslint-disable @typescript-eslint/naming-convention, camelcase */ - const { ahead_by, behind_by } = (await this.options.octokit.repos.compareCommits({ + const { data } = (await this.options.octokit.repos.compareCommits({ ...this.options.repo, base, head - })).data; + })); + + const { ahead_by, behind_by } = data; this.logger.debug(`${baseBranch} is ahead by ${ahead_by} and behind by ${behind_by} compare to ${targetBranch}`); return ahead_by > 0; - /* eslint-enable @typescript-eslint/naming-convention, camelcase */ } /** @inheritdoc */ protected async getBranches() { this.logger.debug('List remote branches'); - /* eslint-disable camelcase, @typescript-eslint/naming-convention */ - const per_page = 100; + const perPage = 100; let pageIndex = 1; let getCurrentPage = true; const branchNames: string[] = []; while (getCurrentPage && pageIndex <= 20) { const res = await this.options.octokit.repos.listBranches({ ...this.options.repo, - per_page, + per_page: perPage, page: pageIndex++ }); branchNames.push(...res.data.map(({ name }) => name)); - getCurrentPage = res.data.length === per_page; + getCurrentPage = res.data.length === perPage; } return branchNames; - /* eslint-enable camelcase, @typescript-eslint/naming-convention */ } /** @inheritdoc */ protected async createBranch(branchName: string, baseBranch: string) { - const sha = (await this.options.octokit.repos.getBranch({ + const { data } = (await this.options.octokit.repos.getBranch({ ...this.options.repo, branch: `refs/heads/${baseBranch}` - })).data.commit.sha; + })); + const sha = data.commit.sha; await this.options.octokit.git.createRef({ ...this.options.repo, ref: `refs/heads/${branchName}`, @@ -185,7 +199,7 @@ export class CascadingProbot extends Cascading { /** @inheritdoc */ protected async createPullRequest(cascadingBranch: string, targetBranch: string, body: string, title: string, labels?: string[]) { // TODO: add auto_merge when allow by the RestAPI (cf: https://github.com/orgs/community/discussions/24719) - const {data} = await this.options.octokit.pulls.create({ + const { data } = await this.options.octokit.pulls.create({ ...this.options.repo, head: `refs/heads/${cascadingBranch}`, base: `refs/heads/${targetBranch}`, @@ -196,7 +210,7 @@ export class CascadingProbot extends Cascading { const id = data.number; await this.options.octokit.issues.addLabels({ ...this.options.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + issue_number: id, labels }); @@ -215,7 +229,7 @@ export class CascadingProbot extends Cascading { protected async updatePullRequestMessage(id: string | number, body: string, title?: string): Promise { const { data } = await this.options.octokit.pulls.update({ ...this.options.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + pull_number: +id, body, title @@ -232,7 +246,8 @@ export class CascadingProbot extends Cascading { /** @inheritdoc */ protected async getPullRequests(baseBranch?: string, targetBranch?: string) { - return (await this.options.octokit.pulls.list(this.options.repo)).data + const { data } = await this.options.octokit.pulls.list(this.options.repo); + return data .filter(({ head, base }) => (!targetBranch || base.ref === targetBranch) && (!baseBranch || head.ref === baseBranch)) .sort((prA, prB) => (Date.parse(prA.created_at) - Date.parse(prB.created_at))) .map((pr): CascadingPullRequestInfo => ({ @@ -250,7 +265,7 @@ export class CascadingProbot extends Cascading { protected async getPullRequestFromId(id: string | number): Promise { const { data } = await this.options.octokit.pulls.get({ ...this.options.repo, - // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase + pull_number: +id }); return { diff --git a/apps/github-cascading-app/src/cascading/cascading.spec.ts b/apps/github-cascading-app/src/cascading/cascading.spec.ts index c3495467db..34d22b253d 100644 --- a/apps/github-cascading-app/src/cascading/cascading.spec.ts +++ b/apps/github-cascading-app/src/cascading/cascading.spec.ts @@ -1,7 +1,17 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -import { Cascading } from './cascading'; -import { BaseLogger, CascadingConfiguration, CascadingPullRequestInfo, CheckConclusion, DEFAULT_CONFIGURATION, PullRequestContext } from './interfaces'; -import { render } from 'ejs'; +import { + render, +} from 'ejs'; +import { + Cascading, +} from './cascading'; +import { + BaseLogger, + CascadingConfiguration, + CascadingPullRequestInfo, + CheckConclusion, + DEFAULT_CONFIGURATION, + PullRequestContext, +} from './interfaces'; const mockBasicTemplate = ` @@ -41,7 +51,6 @@ class JestCascading extends Cascading { } describe('Cascading Application', () => { - let customization: JestCascading; let logger: BaseLogger; @@ -59,7 +68,7 @@ describe('Cascading Application', () => { describe('calculate the branch to re-evaluate function', () => { it('should return undefined when non-cascading pull request', async () => { customization.isCascadingPullRequest = customization.isCascadingPullRequest.mockResolvedValue(false); - await expect(customization.branchToReevaluateCascading({id: 1, body: 'fake PR'})).resolves.toBe(undefined); + await expect(customization.branchToReevaluateCascading({ id: 1, body: 'fake PR' })).resolves.toBe(undefined); expect(logger.info).toHaveBeenCalled(); }); @@ -67,7 +76,7 @@ describe('Cascading Application', () => { customization.isCascadingPullRequest = customization.isCascadingPullRequest.mockResolvedValue(true); await expect(customization.branchToReevaluateCascading({ id: 1, - body: render(mockBasicTemplate, { isConflicting: false, targetBranch: 'main', currentBranch: 'release/0.1', bypassReviewers: true }, {async: false}) + body: render(mockBasicTemplate, { isConflicting: false, targetBranch: 'main', currentBranch: 'release/0.1', bypassReviewers: true }, { async: false }) })).resolves.toBe(undefined); expect(logger.info).toHaveBeenCalled(); }); @@ -85,7 +94,7 @@ describe('Cascading Application', () => { describe('merge cascading pull request', () => { it('should skip the process when disabled via config', async () => { customization.loadConfiguration = customization.loadConfiguration.mockResolvedValue({ ...DEFAULT_CONFIGURATION, bypassReviewers: false }); - await expect(customization.mergeCascadingPullRequest({id: 1}, `${DEFAULT_CONFIGURATION.branchNamePrefix}/1.0.0-1.1.0`, 'success')).resolves.not.toThrow(); + await expect(customization.mergeCascadingPullRequest({ id: 1 }, `${DEFAULT_CONFIGURATION.branchNamePrefix}/1.0.0-1.1.0`, 'success')).resolves.not.toThrow(); expect(logger.info).not.toHaveBeenCalled(); expect(customization.mergePullRequest).not.toHaveBeenCalled(); }); diff --git a/apps/github-cascading-app/src/cascading/cascading.ts b/apps/github-cascading-app/src/cascading/cascading.ts index 8e738721a8..0e60dc6346 100644 --- a/apps/github-cascading-app/src/cascading/cascading.ts +++ b/apps/github-cascading-app/src/cascading/cascading.ts @@ -1,7 +1,22 @@ -import { coerce, compare, parse, valid } from 'semver'; -import { BaseLogger, CascadingConfiguration, CascadingPullRequestInfo, CheckConclusion, PullRequestContext } from './interfaces'; -import { renderFile } from 'ejs'; -import { resolve } from 'node:path'; +import { + resolve, +} from 'node:path'; +import { + renderFile, +} from 'ejs'; +import { + coerce, + compare, + parse, + valid, +} from 'semver'; +import { + BaseLogger, + CascadingConfiguration, + CascadingPullRequestInfo, + CheckConclusion, + PullRequestContext, +} from './interfaces'; /** Mark of the template to determine if the users cancelled the cascading retrigger */ export const CANCEL_RETRIGGER_CASCADING_MARK = '!cancel re-cascading!'; @@ -96,12 +111,12 @@ export abstract class Cascading { protected abstract isBranchAhead(baseBranch: string, targetBranch: string): Promise; /** + * Constructor of the Cascading class * @param logger Logger * @param username User name used for git commands * @param email Email used for git commands */ - constructor(public logger: BaseLogger, public username = 'Auto Cascading', public email = 'cascading@otter.com') { - } + constructor(public logger: BaseLogger, public username = 'Auto Cascading', public email = 'cascading@otter.com') {} /** * Parse Pull Request context from the body @@ -109,13 +124,13 @@ export abstract class Cascading { * @returns {undefined} if the context is not found */ protected retrieveContext(content: string): PullRequestContext | undefined { - const match = content.match(//s); + const match = content.match(//s); if (!match || !match[1]) { this.logger.warn('Failed to parse '); return; } - return JSON.parse(match[1]); + return JSON.parse(match[1]) as PullRequestContext; } /** @@ -129,10 +144,8 @@ export abstract class Cascading { canBeMerged: pullRequest?.mergeable ?? true, id: pullRequest?.id || '', originBranchName: pullRequest?.originBranchName || '', - // eslint-disable-next-line @typescript-eslint/naming-convention - CANCEL_RETRIGGER_CASCADING_MARK, - // eslint-disable-next-line @typescript-eslint/naming-convention - CANCEL_BYPASS_REVIEWERS_MARK + cancelRetriggerCascadingMark: CANCEL_RETRIGGER_CASCADING_MARK, + cancelBypassReviewersMark: CANCEL_BYPASS_REVIEWERS_MARK }); } @@ -171,7 +184,7 @@ export abstract class Cascading { }; } }) - .filter(({branch, semver}) => { + .filter(({ branch, semver }) => { if (semver === null) { this.logger.warn(`Failed to parse the branch ${branch}, it will be skipped from cascading`); return false; @@ -188,7 +201,7 @@ export abstract class Cascading { return compare(branchObjectA.semver, branchObjectB.semver); }); - this.logger.debug('Discovered following branches to cascade ' + JSON.stringify(branchesToCascade.map(({branch}) => branch), null, 2)); + this.logger.debug('Discovered following branches to cascade ' + JSON.stringify(branchesToCascade.map(({ branch }) => branch), null, 2)); return branchesToCascade; } @@ -283,7 +296,9 @@ export abstract class Cascading { this.logger.debug(`Run trigger to cascading PR from ${cascadingBranch}`); const openPr = await this.findOpenPullRequest(cascadingBranch, targetBranch); - if (!openPr) { + if (openPr) { + return this.updatePullRequestWithNewMessage(openPr, openPr.context || { bypassReviewers: config.bypassReviewers, currentBranch, targetBranch, isConflicting: false }); + } else { this.logger.debug(`Will recreate the branch ${cascadingBranch}`); try { await this.deleteBranch(cascadingBranch); @@ -293,8 +308,6 @@ export abstract class Cascading { this.logger.debug(JSON.stringify(error, null, 2)); } return this.createPullRequestWithMessage(cascadingBranch, currentBranch, targetBranch, config, true); - } else { - return this.updatePullRequestWithNewMessage(openPr, openPr.context || { bypassReviewers: config.bypassReviewers, currentBranch, targetBranch, isConflicting: false }); } } @@ -304,7 +317,6 @@ export abstract class Cascading { * @param currentBranch name of the base branch of the cascading process * @param targetBranch name of the branch target (base of the pull request) * @param config - * @param shouldAddUpdateMessage Determine if the body of the new pull request should add the update request message * @param isConflicting */ protected async createPullRequestWithMessage(cascadingBranch: string, currentBranch: string, targetBranch: string, config: CascadingConfiguration, isConflicting = false) { @@ -367,7 +379,7 @@ export abstract class Cascading { const cascadingBranches = this.getOrderedCascadingBranches(branches, config); const branchIndex = cascadingBranches.findIndex(({ branch }) => branch === currentBranchName); - if (branchIndex < 0) { + if (branchIndex === -1) { this.logger.error(`The branch ${currentBranchName} is not part of the list of cascading branch. The process will stop.`); return; } diff --git a/apps/github-cascading-app/src/cascading/templates/pull-request-content.ejs b/apps/github-cascading-app/src/cascading/templates/pull-request-content.ejs index 294714a2a5..bcb07dfbb3 100644 --- a/apps/github-cascading-app/src/cascading/templates/pull-request-content.ejs +++ b/apps/github-cascading-app/src/cascading/templates/pull-request-content.ejs @@ -6,7 +6,7 @@ The configuration requests the cascading to bypass reviewer in case of CI success. To not bypass the reviewing process, please check the following checkbox: -- [ ] :no_entry_sign: stop reviewing process bypass for this Pull Request +- [ ] :no_entry_sign: stop reviewing process bypass for this Pull Request <% } %> <% if (isConflicting) { %> @@ -15,7 +15,7 @@ To not bypass the reviewing process, please check the following checkbox: :warning: An update conflicting with this Pull Request has been done on **<%= currentBranch %>**. The cascading process will re-create the Pull Request after merging this one. To not re-trigger the process, please check the following checkbox: -- [ ] Cancel the re-Trigger of cascading process +- [ ] Cancel the re-Trigger of cascading process <% } %> <% if (!canBeMerged) { %> diff --git a/apps/github-cascading-app/src/github-cascading/index.ts b/apps/github-cascading-app/src/github-cascading/index.ts index 68f8a2aa17..120606d119 100644 --- a/apps/github-cascading-app/src/github-cascading/index.ts +++ b/apps/github-cascading-app/src/github-cascading/index.ts @@ -1,7 +1,15 @@ -import { createProbot } from 'probot'; -import { app as azureApp, type HttpRequest } from '@azure/functions'; +import { + app as azureApp, + type HttpRequest, +} from '@azure/functions'; +import { + createProbot, + type Probot, +} from 'probot'; import app from '../app'; +type EventName = Parameters[0]['name']; + const probotInstance = createProbot(); void probotInstance.load(app); azureApp.http('github-cascading', { @@ -9,12 +17,12 @@ azureApp.http('github-cascading', { handler: async (req: HttpRequest) => { await probotInstance.webhooks.verifyAndReceive({ id: req.headers.get('X-GitHub-Delivery') || req.headers.get('x-github-delivery')!, - name: req.headers.get('X-GitHub-Event') || req.headers.get('x-github-event') as any, + name: (req.headers.get('X-GitHub-Event') || req.headers.get('x-github-event')) as EventName, signature: - req.headers.get('X-Hub-Signature-256') || - req.headers.get('x-hub-signature-256') || - req.headers.get('X-Hub-Signature') || - req.headers.get('x-hub-signature')!, + req.headers.get('X-Hub-Signature-256') + || req.headers.get('x-hub-signature-256') + || req.headers.get('X-Hub-Signature') + || req.headers.get('x-hub-signature')!, payload: await req.text() }); diff --git a/apps/github-cascading-app/testing/jest.config.ut.js b/apps/github-cascading-app/testing/jest.config.ut.js index a6ad0c43ce..7bc929dca3 100644 --- a/apps/github-cascading-app/testing/jest.config.ut.js +++ b/apps/github-cascading-app/testing/jest.config.ut.js @@ -1,5 +1,6 @@ const path = require('node:path'); const getJestProjectConfig = require('../../../jest.config.ut').getJestProjectConfig; + const rootDir = path.join(__dirname, '..'); const baseConfig = getJestProjectConfig(rootDir, false); @@ -7,5 +8,5 @@ const baseConfig = getJestProjectConfig(rootDir, false); /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ module.exports = { ...baseConfig, - displayName: require('../package.json').name, + displayName: require('../package.json').name }; diff --git a/apps/github-cascading-app/tsconfig.eslint.json b/apps/github-cascading-app/tsconfig.eslint.json index 47a21e2d5a..6266c368cb 100644 --- a/apps/github-cascading-app/tsconfig.eslint.json +++ b/apps/github-cascading-app/tsconfig.eslint.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.build", "include": [ - ".eslintrc.js", + "eslint*.config.mjs", "jest.config.js", "testing/**/*.js", "scripts/**/*.mjs" diff --git a/apps/showcase/.eslintrc.js b/apps/showcase/.eslintrc.js deleted file mode 100644 index 46ccc6a5f7..0000000000 --- a/apps/showcase/.eslintrc.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable quote-props */ - -module.exports = { - 'root': true, - 'overrides': [ - { - 'files': [ - '*.{t,j}s' - ], - 'parserOptions': { - 'tsconfigRootDir': __dirname, - 'project': [ - 'tsconfig.app.json', - 'tsconfig.eslint.json', - 'tsconfig.spec.json' - ], - 'sourceType': 'module' - }, - 'rules': { - '@o3r/o3r-widget-tags': [ - 'error', - { - 'widgets': { - 'DESTINATION_ARRAY': { - 'minItems': { - 'type': 'number' - }, - 'allDestinationsDifferent': { - 'type': 'boolean' - }, - 'atLeastOneDestinationAvailable': { - 'type': 'boolean' - }, - 'destinationPattern': { - 'type': 'string' - } - } - } - } - ] - } - }, - { - 'files': [ - '*.html' - ], - 'plugins': [], - 'parser': require.resolve('@angular-eslint/template-parser'), - 'extends': [ - '@o3r/eslint-config-otter/template' - ].map(require.resolve) - } - ], - 'extends': [ - '../../.eslintrc.js' - ] -}; diff --git a/apps/showcase/e2e-playwright/kassette.config.js b/apps/showcase/e2e-playwright/kassette.config.js index 1372ce4e73..e9b22dde06 100644 --- a/apps/showcase/e2e-playwright/kassette.config.js +++ b/apps/showcase/e2e-playwright/kassette.config.js @@ -1,6 +1,5 @@ /** * Configuration of the mock server - * * @returns { import('@amadeus-it-group/kassette').ConfigurationSpec } configuration */ exports.getConfiguration = () => { @@ -15,7 +14,7 @@ exports.getConfiguration = () => { saveForwardedRequestData: false, saveInputRequestBody: false, saveInputRequestData: false, - hook: async ({mock}) => { + hook: async ({ mock }) => { if (/petstore3\.swagger\.io/.test(mock.request.url)) { mock.setMode('local_or_download'); return; diff --git a/apps/showcase/e2e-playwright/playwright-config.sanity.ts b/apps/showcase/e2e-playwright/playwright-config.sanity.ts index 5a1fde2247..57ef4fd627 100644 --- a/apps/showcase/e2e-playwright/playwright-config.sanity.ts +++ b/apps/showcase/e2e-playwright/playwright-config.sanity.ts @@ -1,7 +1,10 @@ -import { defineConfig } from '@playwright/test'; import * as path from 'node:path'; - -import {default as defaultConfig} from './playwright-config'; +import { + defineConfig, +} from '@playwright/test'; +import { + default as defaultConfig, +} from './playwright-config'; const config = defineConfig({ ...defaultConfig, diff --git a/apps/showcase/e2e-playwright/playwright-config.ts b/apps/showcase/e2e-playwright/playwright-config.ts index 3241decd5e..cf69d8b22f 100644 --- a/apps/showcase/e2e-playwright/playwright-config.ts +++ b/apps/showcase/e2e-playwright/playwright-config.ts @@ -1,6 +1,10 @@ -import { adjustPath } from '@o3r/testing/tools/path-replacement'; -import { defineConfig } from '@playwright/test'; import * as path from 'node:path'; +import { + adjustPath, +} from '@o3r/testing/tools/path-replacement'; +import { + defineConfig, +} from '@playwright/test'; adjustPath('playwright'); @@ -12,28 +16,30 @@ const config = defineConfig({ snapshotPathTemplate: '{testDir}/screenshots/{testFilePath}/{arg}{ext}', reporter: [ ['list'], - ['junit', {outputFile: path.join(reportsFolder, 'junit', 'reporter.xml')}], - ['html', {open: 'never', outputFolder: path.join(reportsFolder, 'html')}] + ['junit', { outputFile: path.join(reportsFolder, 'junit', 'reporter.xml') }], + ['html', { open: 'never', outputFolder: path.join(reportsFolder, 'html') }] ], retries: process.env.CI ? 3 : 0, forbidOnly: !!process.env.CI, - navigationTimeout: 10000, - timeout: 60000, + navigationTimeout: 10_000, + timeout: 60_000, use: { ignoreHTTPSErrors: true, screenshot: 'only-on-failure', trace: 'retain-on-failure', - ...process.env.USE_MOCKS ? { - launchOptions: { - proxy: {server: 'http://localhost:4747'}, - args: ['--remote-debugging-port=9222', '--ignore-certificate-errors'] - }, - proxy: {server: 'http://localhost:4747'}, - serviceWorkers: 'block', - ignoreHTTPSErrors: true - } : { - launchOptions: { args: ['--remote-debugging-port=9222']} - } + ...process.env.USE_MOCKS + ? { + launchOptions: { + proxy: { server: 'http://localhost:4747' }, + args: ['--remote-debugging-port=9222', '--ignore-certificate-errors'] + }, + proxy: { server: 'http://localhost:4747' }, + serviceWorkers: 'block', + ignoreHTTPSErrors: true + } + : { + launchOptions: { args: ['--remote-debugging-port=9222'] } + } }, expect: { toHaveScreenshot: { @@ -41,16 +47,18 @@ const config = defineConfig({ } }, projects: [ - {name: 'Chromium', use: {browserName: 'chromium'}} + { name: 'Chromium', use: { browserName: 'chromium' } } ], webServer: [ - ...process.env.USE_MOCKS ? [{ - command: `yarn kassette -c ${path.join(__dirname, 'kassette.config.js')}`, - cwd: path.join(__dirname, '..'), - port: 4747, - timeout: 120 * 1000, - reuseExistingServer: !process.env.CI - }] : [] + ...process.env.USE_MOCKS + ? [{ + command: `yarn kassette -c ${path.join(__dirname, 'kassette.config.js')}`, + cwd: path.join(__dirname, '..'), + port: 4747, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI + }] + : [] ] }); diff --git a/apps/showcase/e2e-playwright/sanity/lighthouse-sanity.e2e.ts b/apps/showcase/e2e-playwright/sanity/lighthouse-sanity.e2e.ts index f6659ec5c2..909fab48eb 100644 --- a/apps/showcase/e2e-playwright/sanity/lighthouse-sanity.e2e.ts +++ b/apps/showcase/e2e-playwright/sanity/lighthouse-sanity.e2e.ts @@ -1,7 +1,17 @@ -import { O3rElement } from '@o3r/testing/core'; -import { type Page, test, type TestInfo } from '@playwright/test'; -import { type playwrightLighthouseConfig } from 'playwright-lighthouse'; -import { AppFixtureComponent } from '../../src/app/app.fixture'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + type Page, + test, + type TestInfo, +} from '@playwright/test'; +import { + type playwrightLighthouseConfig, +} from 'playwright-lighthouse'; +import { + AppFixtureComponent, +} from '../../src/app/app.fixture'; const baseUrl = process.env.PLAYWRIGHT_TARGET_URL || 'http://localhost:4200/'; const lighthouseConfig: playwrightLighthouseConfig = { @@ -9,7 +19,7 @@ const lighthouseConfig: playwrightLighthouseConfig = { // Disable performance measurement because it is too unreliable in the current setup performance: 0, accessibility: 100, - // eslint-disable-next-line @typescript-eslint/naming-convention + 'best-practices': 100 }, opts: { @@ -36,7 +46,7 @@ const performAudit = async (name: string, page: Page | string, testInfo: TestInf } }); } finally { - await testInfo.attach('lighthouse-report', {path: `${lighthouseConfig.reports.directory}/${name}.html`}); + await testInfo.attach('lighthouse-report', { path: `${lighthouseConfig.reports.directory}/${name}.html` }); } }; @@ -47,78 +57,78 @@ const performAudit = async (name: string, page: Page | string, testInfo: TestInf test.describe.configure({ mode: 'serial' }); test.describe('Lighthouse tests', () => { - test('home', async ({page}, testInfo) => { + test('home', async ({ page }, testInfo) => { await page.goto(baseUrl); await performAudit('home', page, testInfo); }); - test('run-app-locally', async ({page}, testInfo) => { + test('run-app-locally', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToRunAppLocally(); await page.waitForURL('**/run-app-locally'); await performAudit('run-app-locally', page, testInfo); }); - test('configuration', async ({page}, testInfo) => { + test('configuration', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToConfiguration(); await page.waitForURL('**/configuration'); await performAudit('configuration', page, testInfo); }); - test('localization', async ({page}, testInfo) => { + test('localization', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToLocalization(); await page.waitForURL('**/localization'); await performAudit('localization', page, testInfo); }); - test('dynamic-content', async ({page}, testInfo) => { + test('dynamic-content', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToDynamicContent(); await page.waitForURL('**/dynamic-content'); await performAudit('dynamic-content', page, testInfo); }); - test('rules-engine', async ({page}, testInfo) => { + test('rules-engine', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToRulesEngine(); await page.waitForURL('**/rules-engine'); await performAudit('rules-engine', page, testInfo); }); - test('component-replacement', async ({page}, testInfo) => { + test('component-replacement', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToComponentReplacement(); await page.waitForURL('**/component-replacement'); await performAudit('component-replacement', page, testInfo); }); - test('design-token', async ({page}, testInfo) => { + test('design-token', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToDesignToken(); await page.waitForURL('**/design-token'); await performAudit('design-token', page, testInfo); }); - test('sdk-generator', async ({page}, testInfo) => { + test('sdk-generator', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToSDKGenerator(); await page.waitForURL('**/sdk'); await performAudit('sdk-generator', page, testInfo); }); - test('placeholder', async ({page}, testInfo) => { + test('placeholder', async ({ page }, testInfo) => { await page.goto(baseUrl); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await appFixture.navigateToPlaceholder(); await page.waitForURL('**/placeholder'); await performAudit('placeholder', page, testInfo); diff --git a/apps/showcase/e2e-playwright/sanity/visual-sanity.e2e.ts b/apps/showcase/e2e-playwright/sanity/visual-sanity.e2e.ts index b0e66c5166..f4a3db9703 100644 --- a/apps/showcase/e2e-playwright/sanity/visual-sanity.e2e.ts +++ b/apps/showcase/e2e-playwright/sanity/visual-sanity.e2e.ts @@ -1,58 +1,65 @@ -import { O3rElement } from '@o3r/testing/core'; -import { expect, test } from '@playwright/test'; -import { AppFixtureComponent } from '../../src/app/app.fixture'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + expect, + test, +} from '@playwright/test'; +import { + AppFixtureComponent, +} from '../../src/app/app.fixture'; test.describe.serial('Sanity test', () => { test('Visual comparison for each page', async ({ browserName, page }) => { await page.clock.install({ time: new Date('2000-01-01T00:00:00') }); await page.goto(process.env.PLAYWRIGHT_TARGET_URL || 'http://localhost:4200/'); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await test.step('home', async () => { await page.waitForURL('**/home'); - await expect(page).toHaveScreenshot([browserName, 'home.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'home.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('run-app-locally', async () => { await appFixture.navigateToRunAppLocally(); await page.waitForURL('**/run-app-locally'); - await expect(page).toHaveScreenshot([browserName, 'run-app-locally.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'run-app-locally.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('configuration', async () => { await appFixture.navigateToConfiguration(); await page.waitForURL('**/configuration'); - await expect(page).toHaveScreenshot([browserName, 'configuration.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'configuration.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('localization', async () => { await appFixture.navigateToLocalization(); await page.waitForURL('**/localization'); - await expect(page).toHaveScreenshot([browserName, 'localization.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'localization.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('dynamic-content', async () => { await appFixture.navigateToDynamicContent(); await page.waitForURL('**/dynamic-content'); - await expect(page).toHaveScreenshot([browserName, 'dynamic-content.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'dynamic-content.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('rules-engine', async () => { await appFixture.navigateToRulesEngine(); await page.waitForURL('**/rules-engine'); - await expect(page).toHaveScreenshot([browserName, 'rules-engine.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'rules-engine.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('component-replacement', async () => { await appFixture.navigateToComponentReplacement(); await page.waitForURL('**/component-replacement'); - await expect(page).toHaveScreenshot([browserName, 'component-replacement.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'component-replacement.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('design-token', async () => { await appFixture.navigateToDesignToken(); await page.waitForURL('**/design-token'); - await expect(page).toHaveScreenshot([browserName, 'design-token.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'design-token.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('sdk-generator', async () => { @@ -60,13 +67,13 @@ test.describe.serial('Sanity test', () => { await appFixture.navigateToSDKGenerator(); await page.waitForURL('**/sdk'); await waitForPetStore; - await expect(page).toHaveScreenshot([browserName, 'sdk-generator.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'sdk-generator.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); await test.step('placeholder', async () => { await appFixture.navigateToPlaceholder(); await page.waitForURL('**/placeholder'); - await expect(page).toHaveScreenshot([browserName, 'placeholder.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]}); + await expect(page).toHaveScreenshot([browserName, 'placeholder.png'], { fullPage: true, mask: [page.locator('.visual-testing-ignore')] }); }); }); }); diff --git a/apps/showcase/e2e-playwright/scenarios/component-replacement-page-scenario.e2e-playwright-spec.ts b/apps/showcase/e2e-playwright/scenarios/component-replacement-page-scenario.e2e-playwright-spec.ts index f363917ca9..c9dd2796c8 100644 --- a/apps/showcase/e2e-playwright/scenarios/component-replacement-page-scenario.e2e-playwright-spec.ts +++ b/apps/showcase/e2e-playwright/scenarios/component-replacement-page-scenario.e2e-playwright-spec.ts @@ -1,13 +1,22 @@ -import { O3rElement } from '@o3r/testing/core'; -import { expect, test } from '@playwright/test'; -import { AppFixtureComponent } from '../../src/app/app.fixture'; -import { ComponentReplacementPresFixtureComponent } from '../../src/components/showcase/component-replacement/component-replacement-pres.fixture'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + expect, + test, +} from '@playwright/test'; +import { + AppFixtureComponent, +} from '../../src/app/app.fixture'; +import { + ComponentReplacementPresFixtureComponent, +} from '../../src/components/showcase/component-replacement/component-replacement-pres.fixture'; test.describe.serial('Test component replacement page', () => { test('Go to component replacement and play with date input', async ({ page }) => { await page.clock.install({ time: new Date('2000-01-01T00:00:00') }); await page.goto(process.env.PLAYWRIGHT_TARGET_URL || 'http://localhost:4200/'); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await test.step('go to component replacement', async () => { await appFixture.navigateToComponentReplacement(); @@ -15,12 +24,12 @@ test.describe.serial('Test component replacement page', () => { }); await test.step('change date', async () => { - const componentReplacementPresFixture = new ComponentReplacementPresFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const componentReplacementPresFixture = new ComponentReplacementPresFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); const dateField = (await componentReplacementPresFixture.getDate())!; const dateInputField = (await componentReplacementPresFixture.getDateInput())!; - expect(await dateField.getText()).toMatch(/\d{4,}-\d{1,2}-\d{1,2}/); - expect(await dateInputField.getValue()).toMatch(/\d{4,}-\d{1,2}-\d{1,2}/); + expect(await dateField.getText()).toMatch(/\d{4,}(?:-\d{1,2}){2}/); + expect(await dateInputField.getValue()).toMatch(/\d{4,}(?:-\d{1,2}){2}/); await dateInputField.setValue('5782-06-01'); expect(await dateField.getText()).toBe('2022-2-2'); diff --git a/apps/showcase/e2e-playwright/scenarios/configuration-page-scenario.e2e-playwright-spec.ts b/apps/showcase/e2e-playwright/scenarios/configuration-page-scenario.e2e-playwright-spec.ts index 9f4f19c2d3..78b5f4c03d 100644 --- a/apps/showcase/e2e-playwright/scenarios/configuration-page-scenario.e2e-playwright-spec.ts +++ b/apps/showcase/e2e-playwright/scenarios/configuration-page-scenario.e2e-playwright-spec.ts @@ -1,13 +1,22 @@ -import { O3rElement } from '@o3r/testing/core'; -import { expect, test } from '@playwright/test'; -import { AppFixtureComponent } from '../../src/app/app.fixture'; -import { ConfigurationFixtureComponent } from '../../src/app/configuration/configuration.fixture'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + expect, + test, +} from '@playwright/test'; +import { + AppFixtureComponent, +} from '../../src/app/app.fixture'; +import { + ConfigurationFixtureComponent, +} from '../../src/app/configuration/configuration.fixture'; test.describe.serial('Test configuration page', () => { test('Go to configuration and play with override button', async ({ page }) => { await page.clock.install({ time: new Date('2000-01-01T00:00:00') }); await page.goto(process.env.PLAYWRIGHT_TARGET_URL || 'http://localhost:4200/'); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await test.step('go to configuration', async () => { await appFixture.navigateToConfiguration(); @@ -15,7 +24,7 @@ test.describe.serial('Test configuration page', () => { }); await test.step('override configuration', async () => { - const configurationFixture = new ConfigurationFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const configurationFixture = new ConfigurationFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); const overrideButton = (await configurationFixture.getOverrideButton())!; const clearOverrideButton = (await configurationFixture.getClearOverrideButton())!; diff --git a/apps/showcase/e2e-playwright/scenarios/dynamic-content-page-scenario.e2e-playwright-spec.ts b/apps/showcase/e2e-playwright/scenarios/dynamic-content-page-scenario.e2e-playwright-spec.ts index 22fc8574a6..a9ee4171eb 100644 --- a/apps/showcase/e2e-playwright/scenarios/dynamic-content-page-scenario.e2e-playwright-spec.ts +++ b/apps/showcase/e2e-playwright/scenarios/dynamic-content-page-scenario.e2e-playwright-spec.ts @@ -1,13 +1,22 @@ -import { O3rElement } from '@o3r/testing/core'; -import { expect, test } from '@playwright/test'; -import { AppFixtureComponent } from '../../src/app/app.fixture'; -import { DynamicContentFixtureComponent } from '../../src/app/dynamic-content/dynamic-content.fixture'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + expect, + test, +} from '@playwright/test'; +import { + AppFixtureComponent, +} from '../../src/app/app.fixture'; +import { + DynamicContentFixtureComponent, +} from '../../src/app/dynamic-content/dynamic-content.fixture'; test.describe.serial('Test dynamic content page', () => { test('Go to dynamic content and play with override button', async ({ page }) => { await page.clock.install({ time: new Date('2000-01-01T00:00:00') }); await page.goto(process.env.PLAYWRIGHT_TARGET_URL || 'http://localhost:4200/'); - const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const appFixture = new AppFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); await test.step('go to dynamic content', async () => { await appFixture.navigateToDynamicContent(); @@ -15,7 +24,7 @@ test.describe.serial('Test dynamic content page', () => { }); await test.step('override dynamic content', async () => { - const dynamicContentFixture = new DynamicContentFixtureComponent(new O3rElement({element: page.locator('app-root'), page})); + const dynamicContentFixture = new DynamicContentFixtureComponent(new O3rElement({ element: page.locator('app-root'), page })); const overrideButton = (await dynamicContentFixture.getOverrideButton())!; const clearOverrideButton = (await dynamicContentFixture.getClearOverrideButton())!; diff --git a/apps/showcase/eslint.config.mjs b/apps/showcase/eslint.config.mjs new file mode 100644 index 0000000000..d86dd85d02 --- /dev/null +++ b/apps/showcase/eslint.config.mjs @@ -0,0 +1,7 @@ +import shared from '../../eslint.shared.config.mjs'; +import local from './eslint.local.config.mjs'; + +export default [ + ...shared, + ...local +]; diff --git a/apps/showcase/eslint.local.config.mjs b/apps/showcase/eslint.local.config.mjs new file mode 100644 index 0000000000..e41af57914 --- /dev/null +++ b/apps/showcase/eslint.local.config.mjs @@ -0,0 +1,73 @@ +import { + dirname, +} from 'node:path'; +import { + fileURLToPath, +} from 'node:url'; +import globals from 'globals'; + +const __filename = fileURLToPath(import.meta.url); +// __dirname is not defined in ES module scope +const __dirname = dirname(__filename); + +export default [ + { + name: '@o3r/showcase/ignores', + ignores: [ + 'dev-resources/**/*', + 'playwright-reports/**/*', + 'test-results/**/*', + '*.metadata.json' + ] + }, + { + name: '@o3r/showcase/projects', + languageOptions: { + sourceType: 'module', + parserOptions: { + tsconfigRootDir: __dirname, + project: [ + 'tsconfig.app.json', + 'tsconfig.eslint.json', + 'tsconfig.spec.json' + ] + } + } + }, + { + name: '@o3r/showcase/playwright', + files: ['**/e2e-playwright/**'], + languageOptions: { + globals: { + ...globals.node + } + } + }, + { + name: '@o3r/showcase/typescript-files', + files: ['**/*.ts'], + rules: { + '@o3r/o3r-widget-tags': [ + 'error', + { + widgets: { + DESTINATION_ARRAY: { + minItems: { + type: 'number' + }, + allDestinationsDifferent: { + type: 'boolean' + }, + atLeastOneDestinationAvailable: { + type: 'boolean' + }, + destinationPattern: { + type: 'string' + } + } + } + } + ] + } + } +]; diff --git a/apps/showcase/package.json b/apps/showcase/package.json index d39c4adb39..ffc1ef076a 100644 --- a/apps/showcase/package.json +++ b/apps/showcase/package.json @@ -91,36 +91,37 @@ "@angular-devkit/build-angular": "~18.2.0", "@angular-devkit/core": "~18.2.0", "@angular-devkit/schematics": "~18.2.0", - "@angular-eslint/eslint-plugin": "~18.3.0", - "@angular-eslint/eslint-plugin-template": "~18.3.0", - "@angular-eslint/template-parser": "~18.3.0", "@angular/cli": "~18.2.0", "@angular/compiler-cli": "~18.2.0", + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0", "@nx/eslint-plugin": "~19.5.0", "@o3r/build-helpers": "workspace:^", "@o3r/design": "workspace:^", - "@o3r/eslint-config-otter": "workspace:^", + "@o3r/eslint-config": "workspace:^", "@o3r/eslint-plugin": "workspace:^", "@o3r/schematics": "workspace:^", "@o3r/testing": "workspace:^", "@playwright/test": "~1.49.0", "@schematics/angular": "~18.2.0", - "@stylistic/eslint-plugin-ts": "~2.4.0", + "@stylistic/eslint-plugin": "~2.7.0", "@types/bootstrap": "^5.2.10", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.14.1", - "@typescript-eslint/types": "^7.14.1", - "@typescript-eslint/utils": "^7.14.1", + "@typescript-eslint/parser": "~8.12.2", "@webcontainer/api": "~1.5.0", + "angular-eslint": "~18.4.0", "concurrently": "^8.0.0", - "eslint": "^8.57.0", + "eslint": "~9.14.0", "eslint-import-resolver-node": "^0.3.9", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import-newlines": "^1.4.0", "eslint-plugin-jest": "~28.8.0", - "eslint-plugin-jsdoc": "~48.11.0", + "eslint-plugin-jsdoc": "~50.2.0", "eslint-plugin-prefer-arrow": "~1.2.3", - "eslint-plugin-unicorn": "^54.0.0", + "eslint-plugin-unicorn": "^56.0.0", + "eslint-plugin-unused-imports": "^4.1.4", + "globals": "^15.9.0", "globby": "^11.1.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", @@ -133,6 +134,7 @@ "rimraf": "^5.0.1", "ts-jest": "~29.2.0", "typescript": "~5.5.4", + "typescript-eslint": "~8.12.2", "webpack": "~5.96.0" } } diff --git a/apps/showcase/project.json b/apps/showcase/project.json index 031c3b95f7..eaf9e7d0f3 100644 --- a/apps/showcase/project.json +++ b/apps/showcase/project.json @@ -39,13 +39,7 @@ ] }, "lint": { - "options": { - "eslintConfig": "apps/showcase/.eslintrc.js", - "lintFilePatterns": [ - "apps/showcase/src/**/*.ts", - "apps/showcase/package.json" - ] - } + "executor": "nx:run-commands" }, "compile": { "executor": "@angular-devkit/build-angular:application", diff --git a/apps/showcase/scripts/update-screenshots/index.cjs b/apps/showcase/scripts/update-screenshots/index.cjs index f43a09c37d..88874afa77 100644 --- a/apps/showcase/scripts/update-screenshots/index.cjs +++ b/apps/showcase/scripts/update-screenshots/index.cjs @@ -4,17 +4,17 @@ * Generate the screenshots for the E2E tests of the showcase app using a docker image running on ubuntu * The goal is to make sure everybody generate the screenshots using the same platform to prevent mismatches */ -const playwrightVersion = require('@playwright/test/package.json').version; const childProcess = require('node:child_process'); const os = require('node:os'); const path = require('node:path'); +const playwrightVersion = require('@playwright/test/package.json').version; const minimist = require('minimist'); const argv = minimist(process.argv.slice(2)); const absolutePathFromRoot = path.resolve(__dirname, '..', '..', '..', '..'); -const relativePathFromRoot = path.relative(absolutePathFromRoot, __dirname).replace(/[\\/]+/g, '/'); +const relativePathFromRoot = path.relative(absolutePathFromRoot, __dirname).replace(/[/\\]+/g, '/'); // The path to mount the local repository inside the container const mountPath = '/tests'; @@ -43,7 +43,9 @@ const args = [ '/bin/bash', pathToPodmanScript, ipAddresses.join(',') -] +]; + +// eslint-disable-next-line no-console -- no other logger available console.log(`Executing: ${script} ${args.join(' ')}`); // Execute the script diff --git a/apps/showcase/src/app/app-routing.module.ts b/apps/showcase/src/app/app-routing.module.ts index 5fe834d86b..2f528e011a 100644 --- a/apps/showcase/src/app/app-routing.module.ts +++ b/apps/showcase/src/app/app-routing.module.ts @@ -1,24 +1,29 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { + NgModule, +} from '@angular/core'; +import { + RouterModule, + Routes, +} from '@angular/router'; const appRoutes: Routes = [ - {path: '', redirectTo: '/home', pathMatch: 'full'}, - {path: 'configuration', loadComponent: () => import('./configuration/index').then((m) => m.ConfigurationComponent), title: 'Otter Showcase - Configuration'}, - {path: 'component-replacement', loadComponent: () => import('./component-replacement/index').then((m) => m.ComponentReplacementComponent), title: 'Otter Showcase - Component replacement'}, - {path: 'design-token', loadComponent: () => import('./design-token/index').then((m) => m.DesignTokenComponent), title: 'Otter Showcase - Design Token'}, - {path: 'localization', loadComponent: () => import('./localization/index').then((m) => m.LocalizationComponent), title: 'Otter Showcase - Localization'}, - {path: 'dynamic-content', loadComponent: () => import('./dynamic-content/index').then((m) => m.DynamicContentComponent), title: 'Otter Showcase - Dynamic Content'}, - {path: 'rules-engine', loadComponent: () => import('./rules-engine/index').then((m) => m.RulesEngineComponent), title: 'Otter Showcase - Rules Engine'}, - {path: 'home', loadComponent: () => import('./home/index').then((m) => m.HomeComponent), title: 'Otter Showcase - Home'}, - {path: 'run-app-locally', loadComponent: () => import('./run-app-locally/index').then((m) => m.RunAppLocallyComponent), title: 'Otter Showcase - Run App Locally'}, - {path: 'sdk', loadComponent: () => import('./sdk/index').then((m) => m.SdkComponent), title: 'Otter Showcase - SDK'}, - {path: 'placeholder', loadComponent: () => import('./placeholder/index').then((m) => m.PlaceholderComponent), title: 'Otter Showcase - Placeholder'}, - {path: '**', redirectTo: '/home', pathMatch: 'full'} + { path: '', redirectTo: '/home', pathMatch: 'full' }, + { path: 'configuration', loadComponent: () => import('./configuration/index').then((m) => m.ConfigurationComponent), title: 'Otter Showcase - Configuration' }, + { path: 'component-replacement', loadComponent: () => import('./component-replacement/index').then((m) => m.ComponentReplacementComponent), title: 'Otter Showcase - Component replacement' }, + { path: 'design-token', loadComponent: () => import('./design-token/index').then((m) => m.DesignTokenComponent), title: 'Otter Showcase - Design Token' }, + { path: 'localization', loadComponent: () => import('./localization/index').then((m) => m.LocalizationComponent), title: 'Otter Showcase - Localization' }, + { path: 'dynamic-content', loadComponent: () => import('./dynamic-content/index').then((m) => m.DynamicContentComponent), title: 'Otter Showcase - Dynamic Content' }, + { path: 'rules-engine', loadComponent: () => import('./rules-engine/index').then((m) => m.RulesEngineComponent), title: 'Otter Showcase - Rules Engine' }, + { path: 'home', loadComponent: () => import('./home/index').then((m) => m.HomeComponent), title: 'Otter Showcase - Home' }, + { path: 'run-app-locally', loadComponent: () => import('./run-app-locally/index').then((m) => m.RunAppLocallyComponent), title: 'Otter Showcase - Run App Locally' }, + { path: 'sdk', loadComponent: () => import('./sdk/index').then((m) => m.SdkComponent), title: 'Otter Showcase - SDK' }, + { path: 'placeholder', loadComponent: () => import('./placeholder/index').then((m) => m.PlaceholderComponent), title: 'Otter Showcase - Placeholder' }, + { path: '**', redirectTo: '/home', pathMatch: 'full' } ]; @NgModule({ imports: [ - RouterModule.forRoot(appRoutes, {scrollPositionRestoration: 'enabled', useHash: true}) + RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled', useHash: true }) ], exports: [RouterModule] }) diff --git a/apps/showcase/src/app/app.component.spec.ts b/apps/showcase/src/app/app.component.spec.ts index 4ebfec9154..ce042cf75c 100644 --- a/apps/showcase/src/app/app.component.spec.ts +++ b/apps/showcase/src/app/app.component.spec.ts @@ -1,15 +1,40 @@ -import { Provider } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { EffectsModule } from '@ngrx/effects'; -import { StoreModule } from '@ngrx/store'; -import { provideMockStore } from '@ngrx/store/testing'; -import { TranslateCompiler, TranslateFakeCompiler } from '@ngx-translate/core'; -import { ApplicationDevtoolsModule } from '@o3r/application'; -import { ComponentsDevtoolsModule } from '@o3r/components'; -import { ConfigurationDevtoolsModule } from '@o3r/configuration'; -import { LocalizationDevtoolsModule } from '@o3r/localization'; -import { mockTranslationModules } from '@o3r/testing/localization'; -import { AppComponent } from './app.component'; +import { + Provider, +} from '@angular/core'; +import { + TestBed, +} from '@angular/core/testing'; +import { + EffectsModule, +} from '@ngrx/effects'; +import { + StoreModule, +} from '@ngrx/store'; +import { + provideMockStore, +} from '@ngrx/store/testing'; +import { + TranslateCompiler, + TranslateFakeCompiler, +} from '@ngx-translate/core'; +import { + ApplicationDevtoolsModule, +} from '@o3r/application'; +import { + ComponentsDevtoolsModule, +} from '@o3r/components'; +import { + ConfigurationDevtoolsModule, +} from '@o3r/configuration'; +import { + LocalizationDevtoolsModule, +} from '@o3r/localization'; +import { + mockTranslationModules, +} from '@o3r/testing/localization'; +import { + AppComponent, +} from './app.component'; const localizationConfiguration = { language: 'en' }; const mockTranslations = { diff --git a/apps/showcase/src/app/app.component.ts b/apps/showcase/src/app/app.component.ts index 902b015d12..219c3b8697 100644 --- a/apps/showcase/src/app/app.component.ts +++ b/apps/showcase/src/app/app.component.ts @@ -1,9 +1,31 @@ -import { Component, inject, OnDestroy, TemplateRef } from '@angular/core'; -import { NavigationEnd, Router } from '@angular/router'; -import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; -import { O3rComponent } from '@o3r/core'; -import { filter, map, Observable, share, shareReplay, Subscription } from 'rxjs'; -import { SideNavLinksGroup } from '../components/index'; +import { + Component, + inject, + OnDestroy, + TemplateRef, +} from '@angular/core'; +import { + NavigationEnd, + Router, +} from '@angular/router'; +import { + NgbOffcanvas, + NgbOffcanvasRef, +} from '@ng-bootstrap/ng-bootstrap'; +import { + O3rComponent, +} from '@o3r/core'; +import { + filter, + map, + Observable, + share, + shareReplay, + Subscription, +} from 'rxjs'; +import { + SideNavLinksGroup, +} from '../components/index'; @O3rComponent({ componentType: 'Component' }) @Component({ diff --git a/apps/showcase/src/app/app.fixture.ts b/apps/showcase/src/app/app.fixture.ts index c6bbf7ecc2..21779081c7 100644 --- a/apps/showcase/src/app/app.fixture.ts +++ b/apps/showcase/src/app/app.fixture.ts @@ -1,5 +1,11 @@ -import { ComponentFixtureProfile, O3rComponentFixture } from '@o3r/testing/core'; -import { SidenavPresFixture, SidenavPresFixtureComponent } from '../components/utilities/sidenav/sidenav-pres.fixture'; +import { + ComponentFixtureProfile, + O3rComponentFixture, +} from '@o3r/testing/core'; +import { + SidenavPresFixture, + SidenavPresFixtureComponent, +} from '../components/utilities/sidenav/sidenav-pres.fixture'; /** * A component fixture abstracts all the interaction you can have with the component's DOM @@ -86,5 +92,4 @@ export class AppFixtureComponent extends O3rComponentFixture implements AppFixtu public async navigateToSDKGenerator() { await (await this.getSideNav()).clickOnLink(9); } - } diff --git a/apps/showcase/src/app/app.module.ts b/apps/showcase/src/app/app.module.ts index 15e66292a2..33e24e41c4 100644 --- a/apps/showcase/src/app/app.module.ts +++ b/apps/showcase/src/app/app.module.ts @@ -1,19 +1,53 @@ -import { ApiFetchClient } from '@ama-sdk/client-fetch'; -import {PetApi} from '@o3r-training/showcase-sdk'; -import { registerLocaleData } from '@angular/common'; +import { + ApiFetchClient, +} from '@ama-sdk/client-fetch'; +import { + registerLocaleData, +} from '@angular/common'; import localeEN from '@angular/common/locales/en'; import localeFR from '@angular/common/locales/fr'; -import { isDevMode, NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgbOffcanvasModule } from '@ng-bootstrap/ng-bootstrap'; -import { EffectsModule } from '@ngrx/effects'; -import { RuntimeChecks, StoreModule } from '@ngrx/store'; -import { StoreDevtoolsModule } from '@ngrx/store-devtools'; -import { TranslateCompiler, TranslateModule } from '@ngx-translate/core'; -import { ApplicationDevtoolsModule, OTTER_APPLICATION_DEVTOOLS_OPTIONS, prefersReducedMotion } from '@o3r/application'; -import { ConfigurationDevtoolsModule, OTTER_CONFIGURATION_DEVTOOLS_OPTIONS } from '@o3r/configuration'; -import { C11nModule, ComponentsDevtoolsModule, OTTER_COMPONENTS_DEVTOOLS_OPTIONS, registerCustomComponent } from '@o3r/components'; +import { + isDevMode, + NgModule, +} from '@angular/core'; +import { + BrowserModule, +} from '@angular/platform-browser'; +import { + BrowserAnimationsModule, +} from '@angular/platform-browser/animations'; +import { + NgbOffcanvasModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { + EffectsModule, +} from '@ngrx/effects'; +import { + RuntimeChecks, + StoreModule, +} from '@ngrx/store'; +import { + StoreDevtoolsModule, +} from '@ngrx/store-devtools'; +import { + TranslateCompiler, + TranslateModule, +} from '@ngx-translate/core'; +import { + ApplicationDevtoolsModule, + OTTER_APPLICATION_DEVTOOLS_OPTIONS, + prefersReducedMotion, +} from '@o3r/application'; +import { + C11nModule, + ComponentsDevtoolsModule, + OTTER_COMPONENTS_DEVTOOLS_OPTIONS, + registerCustomComponent, +} from '@o3r/components'; +import { + ConfigurationDevtoolsModule, + OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, +} from '@o3r/configuration'; import { LocalizationConfiguration, LocalizationDevtoolsModule, @@ -21,17 +55,41 @@ import { MESSAGE_FORMAT_CONFIG, OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, translateLoaderProvider, - TranslateMessageFormatLazyCompiler + TranslateMessageFormatLazyCompiler, } from '@o3r/localization'; -import { ConsoleLogger, Logger, LOGGER_CLIENT_TOKEN, LoggerService } from '@o3r/logger'; -import { OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, RulesEngineRunnerModule } from '@o3r/rules-engine'; -import { OTTER_STYLING_DEVTOOLS_OPTIONS, StylingDevtoolsModule } from '@o3r/styling'; -import { HIGHLIGHT_OPTIONS } from 'ngx-highlightjs'; -import { ScrollBackTopPresComponent, SidenavPresComponent } from '../components/index'; -import { DatePickerHebrewInputPresComponent } from '../components/utilities/date-picker-input-hebrew'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; - +import { + ConsoleLogger, + Logger, + LOGGER_CLIENT_TOKEN, + LoggerService, +} from '@o3r/logger'; +import { + OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, + RulesEngineRunnerModule, +} from '@o3r/rules-engine'; +import { + OTTER_STYLING_DEVTOOLS_OPTIONS, + StylingDevtoolsModule, +} from '@o3r/styling'; +import { + PetApi, +} from '@o3r-training/showcase-sdk'; +import { + HIGHLIGHT_OPTIONS, +} from 'ngx-highlightjs'; +import { + ScrollBackTopPresComponent, + SidenavPresComponent, +} from '../components/index'; +import { + DatePickerHebrewInputPresComponent, +} from '../components/utilities/date-picker-input-hebrew'; +import { + AppRoutingModule, +} from './app-routing.module'; +import { + AppComponent, +} from './app.component'; const runtimeChecks: Partial = { strictActionImmutability: false, @@ -64,7 +122,7 @@ export function localizationConfigurationFactory(): Partial { ], imports: [ BrowserModule, - BrowserAnimationsModule.withConfig({disableAnimations: prefersReducedMotion()}), + BrowserAnimationsModule.withConfig({ disableAnimations: prefersReducedMotion() }), EffectsModule.forRoot([]), StoreModule.forRoot({}, { runtimeChecks }), StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: !isDevMode() }), @@ -106,13 +164,13 @@ export function registerCustomComponents(): Map { ScrollBackTopPresComponent, ApplicationDevtoolsModule, ComponentsDevtoolsModule, - C11nModule.forRoot({registerCompFunc: registerCustomComponents}), + C11nModule.forRoot({ registerCompFunc: registerCustomComponents }), StylingDevtoolsModule, LocalizationDevtoolsModule, ConfigurationDevtoolsModule ], providers: [ - {provide: MESSAGE_FORMAT_CONFIG, useValue: {}}, + { provide: MESSAGE_FORMAT_CONFIG, useValue: {} }, { provide: HIGHLIGHT_OPTIONS, useValue: { @@ -126,14 +184,14 @@ export function registerCustomComponents(): Map { } } }, - {provide: LOGGER_CLIENT_TOKEN, useValue: new ConsoleLogger()}, - {provide: PetApi, useFactory: petApiFactory, deps: [LoggerService]}, - {provide: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}}, - {provide: OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}}, - {provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}}, - {provide: OTTER_COMPONENTS_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}}, - {provide: OTTER_APPLICATION_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true, appName: 'showcase'}}, - {provide: OTTER_STYLING_DEVTOOLS_OPTIONS, useValue: {isActivatedOnBootstrap: true}} + { provide: LOGGER_CLIENT_TOKEN, useValue: new ConsoleLogger() }, + { provide: PetApi, useFactory: petApiFactory, deps: [LoggerService] }, + { provide: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true } }, + { provide: OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true } }, + { provide: OTTER_RULES_ENGINE_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true } }, + { provide: OTTER_COMPONENTS_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true } }, + { provide: OTTER_APPLICATION_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true, appName: 'showcase' } }, + { provide: OTTER_STYLING_DEVTOOLS_OPTIONS, useValue: { isActivatedOnBootstrap: true } } ], bootstrap: [AppComponent] }) diff --git a/apps/showcase/src/app/component-replacement/component-replacement.component.ts b/apps/showcase/src/app/component-replacement/component-replacement.component.ts index 0abdb0ff5a..bcc30a7128 100644 --- a/apps/showcase/src/app/component-replacement/component-replacement.component.ts +++ b/apps/showcase/src/app/component-replacement/component-replacement.component.ts @@ -1,11 +1,36 @@ -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { O3rComponent } from '@o3r/core'; -import { InPageNavLink, InPageNavLinkDirective, InPageNavPresService } from '../../components/utilities/in-page-nav'; -import { IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavPresComponent } from '../../components/utilities/in-page-nav/in-page-nav-pres.component'; -import { AsyncPipe } from '@angular/common'; -import { ComponentReplacementPresComponent } from '../../components/showcase/component-replacement/component-replacement-pres.component'; -import { RouterModule } from '@angular/router'; -import { CopyTextPresComponent } from '../../components/utilities/copy-text/copy-text-pres.component'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + O3rComponent, +} from '@o3r/core'; +import { + ComponentReplacementPresComponent, +} from '../../components/showcase/component-replacement/component-replacement-pres.component'; +import { + CopyTextPresComponent, +} from '../../components/utilities/copy-text/copy-text-pres.component'; +import { + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, +} from '../../components/utilities/in-page-nav'; +import { + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavPresComponent, +} from '../../components/utilities/in-page-nav/in-page-nav-pres.component'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -22,6 +47,7 @@ export class ComponentReplacementComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public ngAfterViewInit() { diff --git a/apps/showcase/src/app/component-replacement/component-replacement.spec.ts b/apps/showcase/src/app/component-replacement/component-replacement.spec.ts index 56c9b62815..954129cadf 100644 --- a/apps/showcase/src/app/component-replacement/component-replacement.spec.ts +++ b/apps/showcase/src/app/component-replacement/component-replacement.spec.ts @@ -1,9 +1,19 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ComponentReplacementComponent } from './component-replacement.component'; -import { RouterModule } from '@angular/router'; -import { AsyncPipe } from '@angular/common'; -import { ComponentReplacementPresComponent } from '../../components/showcase/component-replacement/component-replacement-pres.component'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + ComponentReplacementPresComponent, +} from '../../components/showcase/component-replacement/component-replacement-pres.component'; +import { + ComponentReplacementComponent, +} from './component-replacement.component'; describe('ComponentReplacementComponent', () => { let component: ComponentReplacementComponent; diff --git a/apps/showcase/src/app/configuration/configuration.component.ts b/apps/showcase/src/app/configuration/configuration.component.ts index 728dade313..08629b722f 100644 --- a/apps/showcase/src/app/configuration/configuration.component.ts +++ b/apps/showcase/src/app/configuration/configuration.component.ts @@ -1,10 +1,37 @@ -import { AsyncPipe } from '@angular/common'; -import { AfterViewInit, ChangeDetectionStrategy, Component, computed, inject, QueryList, signal, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ConfigurationBaseServiceModule } from '@o3r/configuration'; -import { O3rComponent } from '@o3r/core'; -import { ConfigurationPresComponent, CopyTextPresComponent, IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavLink, InPageNavLinkDirective, InPageNavPresService } from '../../components/index'; -import { ConfigurationPresConfig } from '../../components/showcase/configuration/configuration-pres.config'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + computed, + inject, + QueryList, + signal, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + ConfigurationBaseServiceModule, +} from '@o3r/configuration'; +import { + O3rComponent, +} from '@o3r/core'; +import { + ConfigurationPresComponent, + CopyTextPresComponent, + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, +} from '../../components/index'; +import { + ConfigurationPresConfig, +} from '../../components/showcase/configuration/configuration-pres.config'; const CONFIG_OVERRIDE: ConfigurationPresConfig = { inXDays: 30, @@ -38,6 +65,7 @@ export class ConfigurationComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public config = signal(undefined); @@ -56,6 +84,6 @@ export class ConfigurationComponent implements AfterViewInit { } public toggleConfig() { - this.config.update((c) => !c ? CONFIG_OVERRIDE : undefined); + this.config.update((c) => c ? undefined : CONFIG_OVERRIDE); } } diff --git a/apps/showcase/src/app/configuration/configuration.fixture.ts b/apps/showcase/src/app/configuration/configuration.fixture.ts index 9692ae4b45..beb98d795c 100644 --- a/apps/showcase/src/app/configuration/configuration.fixture.ts +++ b/apps/showcase/src/app/configuration/configuration.fixture.ts @@ -1,4 +1,8 @@ -import { ComponentFixtureProfile, O3rComponentFixture, O3rElement } from '@o3r/testing/core'; +import { + ComponentFixtureProfile, + O3rComponentFixture, + O3rElement, +} from '@o3r/testing/core'; /** * A component fixture abstracts all the interaction you can have with the component's DOM diff --git a/apps/showcase/src/app/configuration/configuration.spec.ts b/apps/showcase/src/app/configuration/configuration.spec.ts index 9228792f2e..4c6e54353f 100644 --- a/apps/showcase/src/app/configuration/configuration.spec.ts +++ b/apps/showcase/src/app/configuration/configuration.spec.ts @@ -1,10 +1,26 @@ -import { AsyncPipe } from '@angular/common'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterModule } from '@angular/router'; -import { StoreModule } from '@ngrx/store'; -import { O3rElement } from '@o3r/testing/core'; -import { ConfigurationComponent } from './configuration.component'; -import { ConfigurationFixtureComponent } from './configuration.fixture'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + StoreModule, +} from '@ngrx/store'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + ConfigurationComponent, +} from './configuration.component'; +import { + ConfigurationFixtureComponent, +} from './configuration.fixture'; + let componentFixture: ConfigurationFixtureComponent; describe('ConfigurationComponent', () => { diff --git a/apps/showcase/src/app/design-token/design-token.component.ts b/apps/showcase/src/app/design-token/design-token.component.ts index fca6b8e65c..efeb6d93bb 100644 --- a/apps/showcase/src/app/design-token/design-token.component.ts +++ b/apps/showcase/src/app/design-token/design-token.component.ts @@ -1,8 +1,29 @@ -import { AsyncPipe } from '@angular/common'; -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { O3rComponent } from '@o3r/core'; -import { CopyTextPresComponent, DesignTokenPresComponent, IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavLink, InPageNavLinkDirective, InPageNavPresService } from '../../components'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterLink, +} from '@angular/router'; +import { + O3rComponent, +} from '@o3r/core'; +import { + CopyTextPresComponent, + DesignTokenPresComponent, + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, +} from '../../components'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -25,6 +46,7 @@ export class DesignTokenComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public ngAfterViewInit() { diff --git a/apps/showcase/src/app/design-token/design-token.spec.ts b/apps/showcase/src/app/design-token/design-token.spec.ts index ecf2affa47..11a739183e 100644 --- a/apps/showcase/src/app/design-token/design-token.spec.ts +++ b/apps/showcase/src/app/design-token/design-token.spec.ts @@ -1,7 +1,13 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterModule } from '@angular/router'; - -import { DesignTokenComponent } from './design-token.component'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + DesignTokenComponent, +} from './design-token.component'; describe('DesignTokenComponent', () => { let component: DesignTokenComponent; diff --git a/apps/showcase/src/app/dynamic-content/dynamic-content.component.ts b/apps/showcase/src/app/dynamic-content/dynamic-content.component.ts index 75ba288ae0..2081ef569a 100644 --- a/apps/showcase/src/app/dynamic-content/dynamic-content.component.ts +++ b/apps/showcase/src/app/dynamic-content/dynamic-content.component.ts @@ -1,9 +1,32 @@ -import { AsyncPipe } from '@angular/common'; -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { O3rComponent } from '@o3r/core'; -import { DynamicContentModule } from '@o3r/dynamic-content'; -import { CopyTextPresComponent, DynamicContentPresComponent, IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavLink, InPageNavLinkDirective, InPageNavPresService } from '../../components/index'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + O3rComponent, +} from '@o3r/core'; +import { + DynamicContentModule, +} from '@o3r/dynamic-content'; +import { + CopyTextPresComponent, + DynamicContentPresComponent, + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, +} from '../../components/index'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -27,6 +50,7 @@ export class DynamicContentComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public bodyDynamicContentPath = document.body.dataset.dynamiccontentpath; diff --git a/apps/showcase/src/app/dynamic-content/dynamic-content.fixture.ts b/apps/showcase/src/app/dynamic-content/dynamic-content.fixture.ts index 59eee57d19..fc0b3f73d4 100644 --- a/apps/showcase/src/app/dynamic-content/dynamic-content.fixture.ts +++ b/apps/showcase/src/app/dynamic-content/dynamic-content.fixture.ts @@ -1,4 +1,8 @@ -import { ComponentFixtureProfile, O3rComponentFixture, O3rElement } from '@o3r/testing/core'; +import { + ComponentFixtureProfile, + O3rComponentFixture, + O3rElement, +} from '@o3r/testing/core'; /** * A component fixture abstracts all the interaction you can have with the component's DOM diff --git a/apps/showcase/src/app/dynamic-content/dynamic-content.spec.ts b/apps/showcase/src/app/dynamic-content/dynamic-content.spec.ts index d261e29c3a..0ac23aac0c 100644 --- a/apps/showcase/src/app/dynamic-content/dynamic-content.spec.ts +++ b/apps/showcase/src/app/dynamic-content/dynamic-content.spec.ts @@ -1,9 +1,23 @@ -import { AsyncPipe } from '@angular/common'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterModule } from '@angular/router'; -import { O3rElement } from '@o3r/testing/core'; -import { DynamicContentComponent } from './dynamic-content.component'; -import { DynamicContentFixtureComponent } from './dynamic-content.fixture'; +import { + AsyncPipe, +} from '@angular/common'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + O3rElement, +} from '@o3r/testing/core'; +import { + DynamicContentComponent, +} from './dynamic-content.component'; +import { + DynamicContentFixtureComponent, +} from './dynamic-content.fixture'; + let componentFixture: DynamicContentFixtureComponent; describe('DynamicContentComponent', () => { diff --git a/apps/showcase/src/app/home/home.component.ts b/apps/showcase/src/app/home/home.component.ts index 095ced79f9..08fa995896 100644 --- a/apps/showcase/src/app/home/home.component.ts +++ b/apps/showcase/src/app/home/home.component.ts @@ -1,7 +1,17 @@ -import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; -import { O3rComponent } from '@o3r/core'; -import { DynamicContentModule } from '@o3r/dynamic-content'; -import { CopyTextPresComponent } from '../../components'; +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { + O3rComponent, +} from '@o3r/core'; +import { + DynamicContentModule, +} from '@o3r/dynamic-content'; +import { + CopyTextPresComponent, +} from '../../components'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -13,6 +23,4 @@ import { CopyTextPresComponent } from '../../components'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class HomeComponent { - -} +export class HomeComponent {} diff --git a/apps/showcase/src/app/home/home.spec.ts b/apps/showcase/src/app/home/home.spec.ts index 844597a045..71954dd165 100644 --- a/apps/showcase/src/app/home/home.spec.ts +++ b/apps/showcase/src/app/home/home.spec.ts @@ -1,5 +1,10 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { HomeComponent } from './home.component'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + HomeComponent, +} from './home.component'; describe('HomeComponent', () => { let component: HomeComponent; diff --git a/apps/showcase/src/app/localization/localization.component.ts b/apps/showcase/src/app/localization/localization.component.ts index d0ba6cbd70..91e761feb7 100644 --- a/apps/showcase/src/app/localization/localization.component.ts +++ b/apps/showcase/src/app/localization/localization.component.ts @@ -1,8 +1,29 @@ -import { AsyncPipe } from '@angular/common'; -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { O3rComponent } from '@o3r/core'; -import { CopyTextPresComponent, IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavLink, InPageNavLinkDirective, InPageNavPresService, LocalizationPresComponent } from '../../components/index'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + O3rComponent, +} from '@o3r/core'; +import { + CopyTextPresComponent, + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, + LocalizationPresComponent, +} from '../../components/index'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -25,6 +46,7 @@ export class LocalizationComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public ngAfterViewInit() { diff --git a/apps/showcase/src/app/localization/localization.spec.ts b/apps/showcase/src/app/localization/localization.spec.ts index d7aababe73..67770a14a6 100644 --- a/apps/showcase/src/app/localization/localization.spec.ts +++ b/apps/showcase/src/app/localization/localization.spec.ts @@ -1,11 +1,30 @@ -import { AsyncPipe } from '@angular/common'; -import { Provider } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterModule } from '@angular/router'; -import { TranslateCompiler, TranslateFakeCompiler } from '@ngx-translate/core'; -import { LocalizationService } from '@o3r/localization'; -import { mockTranslationModules } from '@o3r/testing/localization'; -import { LocalizationComponent } from './localization.component'; +import { + AsyncPipe, +} from '@angular/common'; +import { + Provider, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + TranslateCompiler, + TranslateFakeCompiler, +} from '@ngx-translate/core'; +import { + LocalizationService, +} from '@o3r/localization'; +import { + mockTranslationModules, +} from '@o3r/testing/localization'; +import { + LocalizationComponent, +} from './localization.component'; + const localizationConfiguration = { language: 'en' }; const mockTranslations = { en: {} diff --git a/apps/showcase/src/app/placeholder/placeholder.component.ts b/apps/showcase/src/app/placeholder/placeholder.component.ts index cb5a6f7377..12f63bef48 100644 --- a/apps/showcase/src/app/placeholder/placeholder.component.ts +++ b/apps/showcase/src/app/placeholder/placeholder.component.ts @@ -1,15 +1,55 @@ -import { AsyncPipe } from '@angular/common'; -import { type AfterViewInit, ChangeDetectionStrategy, Component, inject, type QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { O3rComponent } from '@o3r/core'; -import { PlaceholderRulesEngineActionHandler, PlaceholderRulesEngineActionModule } from '@o3r/components/rules-engine'; -import { DynamicContentModule, DynamicContentService } from '@o3r/dynamic-content'; -import { RulesEngineDevtoolsModule, RulesEngineRunnerModule, RulesEngineRunnerService, RulesetsStore, setRulesetsEntities } from '@o3r/rules-engine'; -import { Store } from '@ngrx/store'; -import { firstValueFrom } from 'rxjs'; -import { IN_PAGE_NAV_PRES_DIRECTIVES, type InPageNavLink, InPageNavLinkDirective, InPageNavPresService, PlaceholderPresComponent } from '../../components'; -import { environment } from '../../environments/environment.development'; -import { TripFactsService } from '../../facts'; +import { + AsyncPipe, +} from '@angular/common'; +import { + type AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + type QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + Store, +} from '@ngrx/store'; +import { + PlaceholderRulesEngineActionHandler, + PlaceholderRulesEngineActionModule, +} from '@o3r/components/rules-engine'; +import { + O3rComponent, +} from '@o3r/core'; +import { + DynamicContentModule, + DynamicContentService, +} from '@o3r/dynamic-content'; +import { + RulesEngineDevtoolsModule, + RulesEngineRunnerModule, + RulesEngineRunnerService, + RulesetsStore, + setRulesetsEntities, +} from '@o3r/rules-engine'; +import { + firstValueFrom, +} from 'rxjs'; +import { + IN_PAGE_NAV_PRES_DIRECTIVES, + type InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, + PlaceholderPresComponent, +} from '../../components'; +import { + environment, +} from '../../environments/environment.development'; +import { + TripFactsService, +} from '../../facts'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -37,6 +77,7 @@ export class PlaceholderComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; constructor() { @@ -50,7 +91,7 @@ export class PlaceholderComponent implements AfterViewInit { private async loadRuleSet() { const path = await firstValueFrom( this.dynamicContentService.getContentPathStream( - `${!environment.production ? 'assets/' : ''}rules/rulesets.json` + `${environment.production ? '' : 'assets/'}rules/rulesets.json` ) ); diff --git a/apps/showcase/src/app/placeholder/placeholder.spec.ts b/apps/showcase/src/app/placeholder/placeholder.spec.ts index 8525b7e1b9..90cda1720d 100644 --- a/apps/showcase/src/app/placeholder/placeholder.spec.ts +++ b/apps/showcase/src/app/placeholder/placeholder.spec.ts @@ -1,8 +1,19 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { EffectsModule } from '@ngrx/effects'; -import { StoreModule } from '@ngrx/store'; -import { RulesEngineRunnerModule } from '@o3r/rules-engine'; -import { PlaceholderComponent } from './placeholder.component'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + EffectsModule, +} from '@ngrx/effects'; +import { + StoreModule, +} from '@ngrx/store'; +import { + RulesEngineRunnerModule, +} from '@o3r/rules-engine'; +import { + PlaceholderComponent, +} from './placeholder.component'; describe('PlaceholderComponent', () => { let component: PlaceholderComponent; diff --git a/apps/showcase/src/app/rules-engine/rules-engine.component.ts b/apps/showcase/src/app/rules-engine/rules-engine.component.ts index a2ad6f48ea..3bb45a4fcb 100644 --- a/apps/showcase/src/app/rules-engine/rules-engine.component.ts +++ b/apps/showcase/src/app/rules-engine/rules-engine.component.ts @@ -1,19 +1,54 @@ -import { AsyncPipe } from '@angular/common'; -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; -import { ApplicationDevtoolsModule } from '@o3r/application'; -import { ComponentsDevtoolsModule } from '@o3r/components'; -import { ConfigOverrideStoreModule, ConfigurationBaseServiceModule, ConfigurationDevtoolsModule } from '@o3r/configuration'; -import { O3rComponent } from '@o3r/core'; -import { AssetPathOverrideStoreModule, DynamicContentService } from '@o3r/dynamic-content'; -import { ConfigurationRulesEngineActionHandler, ConfigurationRulesEngineActionModule } from '@o3r/configuration/rules-engine'; -import { DynamicContentModule } from '@o3r/dynamic-content'; -import { AssetRulesEngineActionHandler, AssetRulesEngineActionModule } from '@o3r/dynamic-content/rules-engine'; -import { LocalizationOverrideStoreModule } from '@o3r/localization'; +import { + AsyncPipe, +} from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + inject, + QueryList, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { + RouterModule, +} from '@angular/router'; +import { + NgbNavModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { + ApplicationDevtoolsModule, +} from '@o3r/application'; +import { + ComponentsDevtoolsModule, +} from '@o3r/components'; +import { + ConfigOverrideStoreModule, + ConfigurationBaseServiceModule, + ConfigurationDevtoolsModule, +} from '@o3r/configuration'; +import { + ConfigurationRulesEngineActionHandler, + ConfigurationRulesEngineActionModule, +} from '@o3r/configuration/rules-engine'; +import { + O3rComponent, +} from '@o3r/core'; +import { + AssetPathOverrideStoreModule, + DynamicContentModule, + DynamicContentService, +} from '@o3r/dynamic-content'; +import { + AssetRulesEngineActionHandler, + AssetRulesEngineActionModule, +} from '@o3r/dynamic-content/rules-engine'; +import { + LocalizationOverrideStoreModule, +} from '@o3r/localization'; import { LocalizationRulesEngineActionHandler, - LocalizationRulesEngineActionModule + LocalizationRulesEngineActionModule, } from '@o3r/localization/rules-engine'; import { CurrentTimeFactsService, @@ -24,13 +59,28 @@ import { RulesEngineRunnerModule, RulesEngineRunnerService, Ruleset, - UnaryOperator + UnaryOperator, } from '@o3r/rules-engine'; -import { firstValueFrom } from 'rxjs'; -import { CopyTextPresComponent, IN_PAGE_NAV_PRES_DIRECTIVES, InPageNavLink, InPageNavLinkDirective, InPageNavPresService, RulesEnginePresComponent } from '../../components/index'; -import { environment } from '../../environments/environment.development'; -import { TripFactsService } from '../../facts/index'; -import { duringSummer } from '../../operators/index'; +import { + firstValueFrom, +} from 'rxjs'; +import { + CopyTextPresComponent, + IN_PAGE_NAV_PRES_DIRECTIVES, + InPageNavLink, + InPageNavLinkDirective, + InPageNavPresService, + RulesEnginePresComponent, +} from '../../components/index'; +import { + environment, +} from '../../environments/environment.development'; +import { + TripFactsService, +} from '../../facts/index'; +import { + duringSummer, +} from '../../operators/index'; @O3rComponent({ componentType: 'Page' }) @Component({ @@ -74,6 +124,7 @@ export class RulesEngineComponent implements AfterViewInit { @ViewChildren(InPageNavLinkDirective) private readonly inPageNavLinkDirectives!: QueryList; + public links$ = this.inPageNavPresService.links$; public activeRuleTab = 'configuration'; @@ -105,12 +156,12 @@ export class RulesEngineComponent implements AfterViewInit { private async loadRuleSet() { const path = await firstValueFrom( this.dynamicContentService.getContentPathStream( - `${!environment.production ? 'assets/' : ''}rules/rulesets.json` + `${environment.production ? '' : 'assets/'}rules/rulesets.json` ) ); const resultCall = await fetch(path); - const result = await resultCall.json() as {rulesets: Ruleset[]}; + const result = await resultCall.json() as { rulesets: Ruleset[] }; this.rulesEngineService.upsertRulesets(result.rulesets); const [ diff --git a/apps/showcase/src/app/rules-engine/rules-engine.spec.ts b/apps/showcase/src/app/rules-engine/rules-engine.spec.ts index 3591c67cb6..7018a59720 100644 --- a/apps/showcase/src/app/rules-engine/rules-engine.spec.ts +++ b/apps/showcase/src/app/rules-engine/rules-engine.spec.ts @@ -1,14 +1,38 @@ -import { AsyncPipe } from '@angular/common'; -import { Provider } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { EffectsModule } from '@ngrx/effects'; -import { StoreModule } from '@ngrx/store'; -import { TranslateCompiler, TranslateFakeCompiler } from '@ngx-translate/core'; -import { LocalizationService } from '@o3r/localization'; -import { RulesEngineRunnerModule } from '@o3r/rules-engine'; -import { mockTranslationModules } from '@o3r/testing/localization'; -import { RulesEngineComponent } from './rules-engine.component'; -import { RouterModule } from '@angular/router'; +import { + AsyncPipe, +} from '@angular/common'; +import { + Provider, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { + RouterModule, +} from '@angular/router'; +import { + EffectsModule, +} from '@ngrx/effects'; +import { + StoreModule, +} from '@ngrx/store'; +import { + TranslateCompiler, + TranslateFakeCompiler, +} from '@ngx-translate/core'; +import { + LocalizationService, +} from '@o3r/localization'; +import { + RulesEngineRunnerModule, +} from '@o3r/rules-engine'; +import { + mockTranslationModules, +} from '@o3r/testing/localization'; +import { + RulesEngineComponent, +} from './rules-engine.component'; const localizationConfiguration = { language: 'en' }; const mockTranslations = { diff --git a/apps/showcase/src/app/rules-engine/rules-engine.template.html b/apps/showcase/src/app/rules-engine/rules-engine.template.html index 8f77711156..51a0c60bd1 100644 --- a/apps/showcase/src/app/rules-engine/rules-engine.template.html +++ b/apps/showcase/src/app/rules-engine/rules-engine.template.html @@ -32,6 +32,7 @@

Example

Source code