@ui5/webcomponents-tools 0.0.0-f42e7c18c → 0.0.0-f6676abdd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,140 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [2.8.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.8.0-rc.0...v2.8.0-rc.1) (2025-02-13)
7
+
8
+ **Note:** Version bump only for package @ui5/webcomponents-tools
9
+
10
+
11
+
12
+
13
+
14
+ # [2.8.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.7.0...v2.8.0-rc.0) (2025-02-06)
15
+
16
+ **Note:** Version bump only for package @ui5/webcomponents-tools
17
+
18
+
19
+
20
+
21
+
22
+ # [2.7.0](https://github.com/SAP/ui5-webcomponents/compare/v2.7.0-rc.2...v2.7.0) (2025-02-03)
23
+
24
+ **Note:** Version bump only for package @ui5/webcomponents-tools
25
+
26
+
27
+
28
+
29
+
30
+ # [2.7.0-rc.2](https://github.com/SAP/ui5-webcomponents/compare/v2.7.0-rc.1...v2.7.0-rc.2) (2025-01-30)
31
+
32
+ **Note:** Version bump only for package @ui5/webcomponents-tools
33
+
34
+
35
+
36
+
37
+
38
+ # [2.7.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.7.0-rc.0...v2.7.0-rc.1) (2025-01-23)
39
+
40
+ **Note:** Version bump only for package @ui5/webcomponents-tools
41
+
42
+
43
+
44
+
45
+
46
+ # [2.7.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.6.2...v2.7.0-rc.0) (2025-01-16)
47
+
48
+ **Note:** Version bump only for package @ui5/webcomponents-tools
49
+
50
+
51
+
52
+
53
+
54
+ ## [2.6.2](https://github.com/SAP/ui5-webcomponents/compare/v2.6.2-rc.0...v2.6.2) (2025-01-09)
55
+
56
+ **Note:** Version bump only for package @ui5/webcomponents-tools
57
+
58
+
59
+
60
+
61
+
62
+ ## [2.6.2-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.6.1...v2.6.2-rc.0) (2025-01-09)
63
+
64
+ **Note:** Version bump only for package @ui5/webcomponents-tools
65
+
66
+
67
+
68
+
69
+
70
+ ## [2.6.1](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0...v2.6.1) (2025-01-08)
71
+
72
+ **Note:** Version bump only for package @ui5/webcomponents-tools
73
+
74
+
75
+
76
+
77
+
78
+ # [2.6.0](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.5...v2.6.0) (2025-01-07)
79
+
80
+ **Note:** Version bump only for package @ui5/webcomponents-tools
81
+
82
+
83
+
84
+
85
+
86
+ # [2.6.0-rc.5](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.4...v2.6.0-rc.5) (2025-01-07)
87
+
88
+ **Note:** Version bump only for package @ui5/webcomponents-tools
89
+
90
+
91
+
92
+
93
+
94
+ # [2.6.0-rc.4](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.3...v2.6.0-rc.4) (2025-01-02)
95
+
96
+ **Note:** Version bump only for package @ui5/webcomponents-tools
97
+
98
+
99
+
100
+
101
+
102
+ # [2.6.0-rc.3](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.2...v2.6.0-rc.3) (2024-12-26)
103
+
104
+ **Note:** Version bump only for package @ui5/webcomponents-tools
105
+
106
+
107
+
108
+
109
+
110
+ # [2.6.0-rc.2](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.1...v2.6.0-rc.2) (2024-12-19)
111
+
112
+
113
+ ### Bug Fixes
114
+
115
+ * **build:** generation of custom-elements.json ([#10403](https://github.com/SAP/ui5-webcomponents/issues/10403)) ([c4eb55c](https://github.com/SAP/ui5-webcomponents/commit/c4eb55cba3e45677c464df1f8cf2a17e6855f2a0))
116
+
117
+
118
+
119
+
120
+
121
+ # [2.6.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.6.0-rc.0...v2.6.0-rc.1) (2024-12-16)
122
+
123
+ **Note:** Version bump only for package @ui5/webcomponents-tools
124
+
125
+
126
+
127
+
128
+
129
+ # [2.6.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.5.0...v2.6.0-rc.0) (2024-12-12)
130
+
131
+
132
+ ### Features
133
+
134
+ * **framework:** add JSX template support with TypeScript ([#10046](https://github.com/SAP/ui5-webcomponents/issues/10046)) ([f42e7c1](https://github.com/SAP/ui5-webcomponents/commit/f42e7c18c846f923df4fec6ae02f1b4c20c006fa))
135
+
136
+
137
+
138
+
139
+
6
140
  # [2.5.0](https://github.com/SAP/ui5-webcomponents/compare/v2.5.0-rc.3...v2.5.0) (2024-12-05)
7
141
 
8
142
  **Note:** Version bump only for package @ui5/webcomponents-tools
package/assets-meta.js CHANGED
@@ -10,10 +10,6 @@ const assetsMeta = {
10
10
  "sap_horizon_dark",
11
11
  "sap_horizon_hcb",
12
12
  "sap_horizon_hcw",
13
- "sap_horizon_exp",
14
- "sap_horizon_dark_exp",
15
- "sap_horizon_hcb_exp",
16
- "sap_horizon_hcw_exp",
17
13
  ],
18
14
  },
19
15
  "languages": {
@@ -36,4 +36,31 @@
36
36
  // }
37
37
  // }
38
38
 
39
- import "cypress-real-events";
39
+ import "cypress-real-events";
40
+
41
+ const realEventCmdCallback = (originalFn, element, ...args) => {
42
+ cy.get(element)
43
+ .should($el => {
44
+ if ($el[0].tagName.includes("-") && $el[0].shadowRoot) {
45
+ expect($el[0].shadowRoot.hasChildNodes(), "Custom elements with shadow DOM have content in their shadow DOM").to.be.true;
46
+ }
47
+ })
48
+ .and("be.visible")
49
+ .then(() => {
50
+ return originalFn(element, ...args)
51
+ });
52
+ };
53
+
54
+ const commands = [
55
+ "realClick",
56
+ "realHover",
57
+ "realTouch",
58
+ "realSwipe",
59
+ "realMouseDown",
60
+ "realMouseUp",
61
+ "realMouseMove"
62
+ ];
63
+
64
+ commands.forEach(cmd => {
65
+ Cypress.Commands.overwrite(cmd, realEventCmdCallback)
66
+ });
@@ -1,5 +1,4 @@
1
1
  /// <reference types="cypress" />
2
- import { RenderOptions, HTMLTemplateResult } from 'lit';
3
2
  import "cypress-real-events";
4
3
 
5
4
  export type Renderable = HTMLTemplateResult;
@@ -7,13 +6,13 @@ export interface MountUI5Options extends MountLitTemplateOptions {
7
6
  ui5Configuration: object;
8
7
  }
9
8
  export type MountOptions = Partial<MountUI5Options>;
10
- export declare function mount<T extends keyof HTMLElementTagNameMap = any>(component: string | Renderable, options?: MountOptions): Cypress.Chainable<JQuery<HTMLElementTagNameMap[T]>>;
9
+ export declare function mount(component: JSX.Element, options?: MountOptions): CypressChainable<JQuery<HTMLElement>>;
11
10
  declare global {
12
11
  namespace Cypress {
13
12
  interface Chainable {
14
13
  /**
15
14
  * Mount your component into Cypress sandbox
16
- * @param component content to render by lit-html render function
15
+ * @param component content to render by preact render function
17
16
  * @param options render options for custom rendering
18
17
  */
19
18
  mount: typeof mount;
@@ -1,34 +1,46 @@
1
- import { setupHooks } from '@cypress/mount-utils';
2
- import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
3
- import { mount } from 'cypress-ct-lit'
1
+ import "@cypress/code-coverage/support";
2
+ import { setupHooks, getContainerEl } from "@cypress/mount-utils";
3
+ import { mount as preactMount } from "./cypress-ct-preact.js";
4
4
  import "./commands.js";
5
5
 
6
- let dispose;
6
+ function applyConfiguration(options) {
7
+ const configurationScript = document.head.querySelector("script[data-ui5-config]");
8
+
9
+ if (options.ui5Configuration) {
10
+ configurationScript.innerHTML = JSON.stringify(options.ui5Configuration);
11
+ }
12
+ }
7
13
 
8
14
  function cleanup() {
9
- dispose?.();
15
+ preactMount(null, getContainerEl());
10
16
  }
11
17
 
12
- function ui5Mount(component, options = {}) {
13
- const configurationScript = document.head.querySelector("script[data-ui5-config]")
14
- cleanup();
18
+ function mount(component, options = {}) {
19
+ const container = getContainerEl();
15
20
 
16
- if (options.ui5Configuration) {
17
- configurationScript.innerHTML = JSON.stringify(options.ui5Configuration);
21
+ // Apply custom configuration
22
+ applyConfiguration(options);
18
23
 
19
- }
24
+ // Mount JSX Element
25
+ preactMount(component, container);
20
26
 
21
- dispose = () => {
22
- configurationScript.innerHTML = "{}";
23
- }
27
+ cy.get(container)
28
+ .find("*")
29
+ .should($el => {
30
+ const shadowrootsExist = [...$el].every(el => {
31
+ if (el.tagName.includes("-") && el.shadowRoot) {
32
+ return el.shadowRoot.hasChildNodes();
33
+ }
24
34
 
25
- if (typeof component === "string") {
26
- return mount(unsafeHTML(component), options)
27
- }
35
+ return true;
36
+ })
37
+
38
+ expect(shadowrootsExist, "Custom elements with shadow DOM have content in their shadow DOM").to.be.true;
39
+ })
28
40
 
29
- return mount(component, options)
41
+ return cy.get(container);
30
42
  }
31
43
 
32
44
  setupHooks(cleanup);
33
45
 
34
- Cypress.Commands.add('mount', ui5Mount)
46
+ Cypress.Commands.add('mount', mount)
@@ -0,0 +1,11 @@
1
+ import { render } from 'preact';
2
+
3
+ function mount(component, container) {
4
+ render(null, container);
5
+
6
+ return render(component, container);
7
+ }
8
+
9
+ export {
10
+ mount,
11
+ };
@@ -1,13 +1,27 @@
1
1
  const { defineConfig } = require('cypress')
2
2
  const path = require("path");
3
+ const coverageTask = require('@cypress/code-coverage/task');
4
+
5
+ const suites = {
6
+ SUITE1: [
7
+ "**/specs/base/*.cy.{jsx,tsx}",
8
+ "**/specs/[A-I]*.cy.{js,jsx,ts,tsx}",
9
+ ],
10
+ SUITE2: [
11
+ "**/specs/[^A-I]*.cy.{js,jsx,ts,tsx}",
12
+ ],
13
+ };
3
14
 
4
15
  module.exports = defineConfig({
5
16
  component: {
17
+ setupNodeEvents(on, config) {
18
+ coverageTask(on, config)
19
+ return config
20
+ },
6
21
  supportFile: path.join(__dirname, "cypress/support/component.js"),
7
22
  indexHtmlFile: path.join(__dirname, "cypress/support/component-index.html"),
8
- specPattern: ["**/specs/*.cy.{js,ts}", "**/specs/**/*.cy.{js,ts}"],
23
+ specPattern: suites[process.env.TEST_SUITE] || ["**/specs/**/*.cy.{js,ts,jsx,tsx}"],
9
24
  devServer: {
10
- framework: 'cypress-ct-lit',
11
25
  bundler: 'vite',
12
26
  }
13
27
  },
@@ -16,4 +30,4 @@ module.exports = defineConfig({
16
30
  scrollBehavior: false,
17
31
  viewportHeight: 1080,
18
32
  viewportWidth: 1440,
19
- })
33
+ })
@@ -4,9 +4,9 @@ const tsMode = fs.existsSync(path.join(process.cwd(), "tsconfig.json"));
4
4
 
5
5
  /**
6
6
  * Returns eslint rules specific to typescript files
7
- * @returns
7
+ * @returns
8
8
  */
9
- const getTsModeOverrides = () => {
9
+ const getTsModeOverrides = () => {
10
10
  const tsConfiguration = {
11
11
  files: ["*.ts"],
12
12
  parser: "@typescript-eslint/parser",
@@ -25,6 +25,7 @@ const getTsModeOverrides = () => {
25
25
  rules: {
26
26
  "no-shadow": "off",
27
27
  "@typescript-eslint/consistent-type-imports": "error",
28
+ "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
28
29
  "@typescript-eslint/no-shadow": ["error"],
29
30
  "@typescript-eslint/no-unsafe-member-access": "off",
30
31
  "@typescript-eslint/no-floating-promises": "off",
@@ -41,8 +42,10 @@ const getTsModeOverrides = () => {
41
42
 
42
43
  const tsxConfiguration = JSON.parse(JSON.stringify(tsConfiguration));
43
44
  tsxConfiguration.files = ["*.tsx"];
45
+ tsxConfiguration.plugins.push("jsx-no-leaked-values");
44
46
  tsxConfiguration.rules = {
45
47
  ...tsxConfiguration.rules,
48
+ "jsx-no-leaked-values/jsx-no-leaked-values": "error",
46
49
  "@typescript-eslint/unbound-method": "off", // to be able to attach on* listeners
47
50
  "@typescript-eslint/no-misused-promises": "off", // to be able to have async event listeners
48
51
  "operator-linebreak": "off",
@@ -53,7 +56,7 @@ const getTsModeOverrides = () => {
53
56
  };
54
57
 
55
58
  const cypressConfiguration = {
56
- "files": ["**/cypress/**/*.ts"],
59
+ "files": ["**/cypress/**/*.ts", "**/cypress/**/*.tsx"],
57
60
 
58
61
  "plugins": [
59
62
  "cypress"
@@ -66,6 +69,7 @@ const getTsModeOverrides = () => {
66
69
  },
67
70
  "rules": {
68
71
  "max-nested-callbacks": 0,
72
+ "no-unused-expressions": 0,
69
73
  "@typescript-eslint/no-namespace": "off",
70
74
  "cypress/no-assigning-return-values": "error",
71
75
  "cypress/no-unnecessary-waiting": "error",
@@ -19,7 +19,7 @@ const getScripts = (options) => {
19
19
  // The script creates the "src/generated/js-imports/Illustration.js" file that registers loaders (dynamic JS imports) for each illustration
20
20
  const createIllustrationsLoadersScript = illustrationsData.map(illustrations => `node ${LIB}/generate-js-imports/illustrations.js ${illustrations.destinationPath} ${illustrations.dynamicImports.outputFile} ${illustrations.set} ${illustrations.collection} ${illustrations.dynamicImports.location} ${illustrations.dynamicImports.filterOut.join(" ")}`).join(" && ");
21
21
 
22
- const tsOption = !options.legacy;
22
+ const tsOption = !options.legacy || options.jsx;
23
23
  const tsCommandOld = tsOption ? "tsc" : "";
24
24
  let tsWatchCommandStandalone = tsOption ? "tsc --watch" : "";
25
25
  // this command is only used for standalone projects. monorepo projects get their watch from vite, so opt-out here
@@ -122,8 +122,11 @@ const getScripts = (options) => {
122
122
  },
123
123
  start: "nps prepare watch.devServer",
124
124
  test: `node "${LIB}/test-runner/test-runner.js"`,
125
- "test-cy-ci": `yarn cypress run --component --browser chrome`,
126
- "test-cy-open": `yarn cypress open --component --browser chrome`,
125
+ "test-cy-ci": `cross-env CYPRESS_COVERAGE=true yarn cypress run --component --browser chrome`,
126
+ "test-cy-ci-suite-1": `cross-env CYPRESS_COVERAGE=true TEST_SUITE=SUITE1 yarn cypress run --component --browser chrome`,
127
+ "test-cy-ci-suite-2": `cross-env CYPRESS_COVERAGE=true TEST_SUITE=SUITE2 yarn cypress run --component --browser chrome`,
128
+ "test-cy-open": `cross-env CYPRESS_COVERAGE=true yarn cypress open --component --browser chrome`,
129
+ "test-cy-single": `cross-env yarn cypress run --component --browser chrome --spec ${process.argv[3]}`,
127
130
  "test-suite-1": `node "${LIB}/test-runner/test-runner.js" --suite suite1`,
128
131
  "test-suite-2": `node "${LIB}/test-runner/test-runner.js" --suite suite2`,
129
132
  startWithScope: "nps scope.prepare scope.watchWithBundle",
@@ -1,13 +1,9 @@
1
1
  // vite.config.js
2
- import { defineConfig } from 'vite';
3
- import virtualIndex from '../lib/dev-server/virtual-index-html-plugin.js';
2
+ const virtualIndex = require('../lib/dev-server/virtual-index-html-plugin.js');
4
3
 
5
- export default defineConfig(async () => {
6
- const data = await virtualIndex();
7
- return {
8
- build: {
9
- emptyOutDir: false,
10
- },
11
- plugins: [data],
12
- }
13
- })
4
+ module.exports = {
5
+ build: {
6
+ emptyOutDir: false,
7
+ },
8
+ plugins: [virtualIndex()],
9
+ };
@@ -60,7 +60,7 @@ exports.config = {
60
60
  // to run chrome headless the following flags are required
61
61
  // (see https://developers.google.com/web/updates/2017/04/headless-chrome)
62
62
  args: [
63
- '--headless=old',
63
+ '--headless',
64
64
  '--disable-search-engine-choice-screen',
65
65
  '--start-maximized',
66
66
  '--no-sandbox',
@@ -127,9 +127,26 @@ function processClass(ts, classNode, moduleDoc) {
127
127
  }
128
128
 
129
129
  // Events
130
- currClass.events = findAllDecorators(classNode, "event")
130
+ currClass.events = findAllDecorators(classNode, ["event", "eventStrict"])
131
131
  ?.map(event => processEvent(ts, event, classNode, moduleDoc));
132
132
 
133
+ // TODO: remove after changing Button's click to custom event.
134
+ // Currently, the Button emits a native click that doesn't need and doesn't have an event decorator,
135
+ // so we add it manually to the events array.
136
+ if (currClass.tagName === "ui5-button") {
137
+ currClass.events.push({
138
+ "name": "click",
139
+ "_ui5privacy": "public",
140
+ "type": {
141
+ "text": "Event"
142
+ },
143
+ "description": "Fired when the component is activated either with a\nmouse/tap or by using the Enter or Space key.\n\n**Note:** The event will not be fired if the `disabled`\nproperty is set to `true`.",
144
+ "_ui5Cancelable": false,
145
+ "_ui5allowPreventDefault": false,
146
+ "_ui5Bubbles": true
147
+ });
148
+ }
149
+
133
150
  const filename = classNode.getSourceFile().fileName;
134
151
  const sourceFile = typeProgram.getSourceFile(filename);
135
152
  const tsProgramClassNode = sourceFile.statements.find(statement => ts.isClassDeclaration(statement) && statement.name?.text === classNode.name?.text);
package/lib/cem/event.mjs CHANGED
@@ -112,12 +112,12 @@ function processEvent(ts, event, classNode, moduleDoc) {
112
112
  : sinceTag.name;
113
113
  }
114
114
 
115
- const eventDetailType = classNode.members?.find(member => member.name.text === "eventDetails")?.type;
115
+ const eventDetailType = classNode.members?.find(member => {
116
+ return ts.isPropertyDeclaration(member) && member.name.text === "eventDetails"
117
+ })?.type;
116
118
  const eventDetailRef = eventDetailType?.members?.find(member => member.name.text === name) || eventDetailType?.types?.[eventDetailType?.types?.length - 1]?.members?.find(member => member.name.text === name);
117
119
  const hasGeneric = !!event?.expression?.typeArguments
118
- // if (name ==="value-state-change") {
119
- // debugger
120
- // }
120
+
121
121
  if (commentParams.length) {
122
122
  if (eventDetailRef && hasGeneric) {
123
123
  logDocumentationError(moduleDoc.path, `Event details for event '${name}' has to be defined either with generic or with eventDetails.`)
package/lib/cem/utils.mjs CHANGED
@@ -3,6 +3,8 @@ import path from "path";
3
3
 
4
4
  let documentationErrors = new Map();
5
5
 
6
+ const packageRegex = /^((@([a-z0-9._-]+)\/)?([a-z0-9._-]+))/;
7
+
6
8
  const getDeprecatedStatus = (jsdocComment) => {
7
9
  const deprecatedTag = findTag(jsdocComment, "deprecated");
8
10
  return deprecatedTag?.name
@@ -30,7 +32,7 @@ const toKebabCase = str => {
30
32
  }
31
33
 
32
34
  const normalizeDescription = (description) => {
33
- return typeof description === 'string' ? description.replaceAll(/^-\s+|^(\n)+|(\n)+$/g, ""): description;
35
+ return typeof description === 'string' ? description.replaceAll(/^-\s+|^(\n)+|(\n)+$/g, "") : description;
34
36
  }
35
37
 
36
38
  const getTypeRefs = (ts, node, member) => {
@@ -111,10 +113,22 @@ const findPackageName = (ts, sourceFile, typeName) => {
111
113
  if (currentModuleSpecifier?.text?.startsWith(".")) {
112
114
  return packageJSON?.name;
113
115
  } else {
114
- return Object.keys(packageJSON?.dependencies || {}).find(
115
- (dependency) =>
116
- currentModuleSpecifier?.text?.startsWith(`${dependency}/`)
117
- );
116
+ // my-package/test
117
+ // my-package
118
+ // @scope/my-package
119
+ // my.package
120
+ // _mypackage
121
+ // mypackage-
122
+ // scope/my-package/test
123
+ // @scope/my-package/test
124
+ const match = currentModuleSpecifier?.text.match(packageRegex);
125
+ let packageName;
126
+
127
+ if (match) {
128
+ packageName = match[1];
129
+ }
130
+
131
+ return packageName || undefined;
118
132
  }
119
133
  }
120
134
  };
@@ -156,33 +170,24 @@ const findImportPath = (ts, sourceFile, typeName, modulePath) => {
156
170
  ?.replace("src", "dist")?.replace(".ts", ".js") || undefined
157
171
  );
158
172
  } else {
159
- // my-package/test
160
- // my-package
161
- // @scope/my-package
162
- // my.package
163
- // _mypackage
164
- // mypackage-
165
- // scope/my-package/test
166
- // @scope/my-package/test
167
- const match = currentModuleSpecifier?.text.match(/^((@([a-z0-9._-]+)\/)?([a-z0-9._-]+))/);
168
- let packageName;
173
+ let packageName = currentModuleSpecifier?.text?.replace(packageRegex, "") || undefined;
169
174
 
170
- if (match) {
171
- packageName = match[1];
175
+ if (packageName?.startsWith("/")) {
176
+ packageName = packageName.replace("/", "");
172
177
  }
173
178
 
174
- return packageName || undefined;
179
+ return packageName;
175
180
  }
176
181
  }
177
182
  };
178
183
 
179
184
 
180
185
  const isClass = text => {
181
- return text.includes("@abstract") || text.includes("@class") || text.includes("@constructor");
186
+ return text.includes("@abstract") || text.includes("@class") || text.includes("@constructor");
182
187
  };
183
188
 
184
189
  const normalizeTagType = (type) => {
185
- return type?.trim();
190
+ return type?.trim();
186
191
  }
187
192
 
188
193
  const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
@@ -258,12 +263,22 @@ const findDecorator = (node, decoratorName) => {
258
263
  };
259
264
 
260
265
  const findAllDecorators = (node, decoratorName) => {
261
- return (
262
- node?.decorators?.filter(
263
- (decorator) =>
264
- decorator?.expression?.expression?.text === decoratorName
265
- ) || []
266
- );
266
+ if (typeof decoratorName === "string") {
267
+ return node?.decorators?.filter(decorator => decorator?.expression?.expression?.text === decoratorName ) || [];
268
+ }
269
+
270
+ if (Array.isArray(decoratorName)) {
271
+ return node?.decorators?.filter(decorator => {
272
+ if (decorator?.expression?.expression?.text) {
273
+ return decoratorName.includes(decorator.expression.expression.text);
274
+ }
275
+
276
+ return false;
277
+ }
278
+ ) || [];
279
+ }
280
+
281
+ return [];
267
282
  };
268
283
 
269
284
  const hasTag = (jsDoc, tagName) => {
@@ -339,7 +354,7 @@ const validateJSDocComment = (fieldType, jsdocComment, node, moduleDoc) => {
339
354
  let isValid = false
340
355
 
341
356
  if (fieldType === "event" && tag?.tag === "param") {
342
- isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag({...tag, tag: "eventparam"});
357
+ isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag({ ...tag, tag: "eventparam" });
343
358
  } else {
344
359
  isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag(tag);
345
360
  }
@@ -368,20 +383,20 @@ const displayDocumentationErrors = () => {
368
383
  [...documentationErrors.keys()].forEach(modulePath => {
369
384
  const moduleErrors = documentationErrors.get(modulePath);
370
385
 
371
- console.log(`=== ERROR: ${moduleErrors.length > 1 ? `${moduleErrors.length} problems` : "Problem"} found in file: ${modulePath}:`)
386
+ console.log(`=== ERROR: ${moduleErrors.length > 1 ? `${moduleErrors.length} problems` : "Problem"} found in file: ${modulePath}:`)
372
387
  moduleErrors.forEach(moduleError => {
373
388
  errorsCount++;
374
389
  console.log(`\t- ${moduleError}`)
375
390
  })
376
391
  })
377
392
 
378
- if(errorsCount) {
393
+ if (errorsCount) {
379
394
  throw new Error(`Found ${errorsCount} errors in the description of the public API.`);
380
395
  }
381
396
  }
382
397
 
383
398
  const formatArrays = (typeText) => {
384
- return typeText?.replaceAll(/(\S+)\[\]/g, "Array<$1>")
399
+ return typeText?.replaceAll(/(\S+)\[\]/g, "Array<$1>")
385
400
  }
386
401
 
387
402
  export {
@@ -1,12 +1,12 @@
1
- const tsFileContentTemplate = (componentName, tagName, library, packageName) => {
1
+ const Component = (componentName, tagName, library, packageName) => {
2
2
  return `import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
3
3
  import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
4
4
  import property from "@ui5/webcomponents-base/dist/decorators/property.js";
5
5
  import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
6
- import event from "@ui5/webcomponents-base/dist/decorators/event.js";
7
- import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
6
+ import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
7
+ import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
8
8
 
9
- import ${componentName}Template from "./generated/templates/${componentName}Template.lit.js";
9
+ import ${componentName}Template from "./${componentName}Template.js";
10
10
 
11
11
  // Styles
12
12
  import ${componentName}Css from "./generated/themes/${componentName}.css.js";
@@ -30,10 +30,9 @@ import ${componentName}Css from "./generated/themes/${componentName}.css.js";
30
30
  */
31
31
  @customElement({
32
32
  tag: "${tagName}",
33
- renderer: litRender,
33
+ renderer: jsxRenderer,
34
34
  styles: ${componentName}Css,
35
35
  template: ${componentName}Template,
36
- dependencies: [],
37
36
  })
38
37
 
39
38
  /**
@@ -42,8 +41,12 @@ import ${componentName}Css from "./generated/themes/${componentName}.css.js";
42
41
  *
43
42
  * @public
44
43
  */
45
- @event("interact", { detail: { /* event payload ( optional ) */ } })
44
+ @event("interact")
46
45
  class ${componentName} extends UI5Element {
46
+ eventDetails!: {
47
+ "interact": void,
48
+ };
49
+
47
50
  /**
48
51
  * Defines the value of the component.
49
52
  *
@@ -68,4 +71,4 @@ export default ${componentName};
68
71
  `;
69
72
  };
70
73
 
71
- module.exports = tsFileContentTemplate;
74
+ module.exports = Component;
@@ -0,0 +1,12 @@
1
+ const ComponentTemplate = (componentName) => {
2
+ return `import type ${componentName} from "./${componentName}.js";
3
+
4
+ export default function ${componentName}Template(this: ${componentName}) {
5
+ return (
6
+ <div>Hello World!</div>
7
+ );
8
+ }
9
+ `;
10
+ };
11
+
12
+ module.exports = ComponentTemplate;
@@ -1,6 +1,7 @@
1
1
  const fs = require("fs");
2
2
  const prompts = require("prompts");
3
- const tsFileContentTemplate = require("./tsFileContentTemplate.js");
3
+ const Component = require("./Component.js");
4
+ const ComponentTemplate= require("./ComponentTemplate.js");
4
5
 
5
6
  /**
6
7
  * Hyphanates the given PascalCase string, f.e.:
@@ -61,12 +62,12 @@ const generateFiles = (componentName, tagName, library, packageName) => {
61
62
  const filePaths = {
62
63
  "main": `./src/${componentName}.ts`,
63
64
  "css": `./src/themes/${componentName}.css`,
64
- "template": `./src/${componentName}.hbs`,
65
+ "template": `./src/${componentName}Template.tsx`,
65
66
  };
66
67
 
67
- fs.writeFileSync(filePaths.main, tsFileContentTemplate(componentName, tagName, library, packageName), { flag: "wx+" });
68
+ fs.writeFileSync(filePaths.main, Component(componentName, tagName, library, packageName), { flag: "wx+" });
68
69
  fs.writeFileSync(filePaths.css, "", { flag: "wx+" });
69
- fs.writeFileSync(filePaths.template, "<div>Hello World</div>", { flag: "wx+" });
70
+ fs.writeFileSync(filePaths.template, ComponentTemplate(componentName), { flag: "wx+" });
70
71
 
71
72
  console.log(`Successfully generated ${filePaths.main}`);
72
73
  console.log(`Successfully generated ${filePaths.css}`);
@@ -74,8 +75,8 @@ const generateFiles = (componentName, tagName, library, packageName) => {
74
75
 
75
76
  // Change the color of the output
76
77
  console.warn('\x1b[33m%s\x1b[0m', `
77
- Make sure to import the component in your bundle by using:
78
- import "./dist/${componentName}.js";`);
78
+ Now, import the component via: "import ${componentName} from ./${componentName}.js";
79
+ And, add it to your HTML: <${tagName}></${tagName}>.`);
79
80
  }
80
81
 
81
82
  // Main function
@@ -28,7 +28,7 @@ let customPlugin = {
28
28
 
29
29
  // JS/TS
30
30
  const jsPath = f.path.replace(/dist[\/\\]css/, "src/generated/").replace(".css", extension);
31
- const jsContent = getFileContent(tsMode, jsPath, packageJSON.name, "\`" + newText + "\`", true);
31
+ const jsContent = getFileContent(packageJSON.name, "\`" + newText + "\`", true);
32
32
  writeFileIfChanged(jsPath, jsContent);
33
33
  });
34
34
  })
@@ -42,16 +42,11 @@ let scopingPlugin = {
42
42
  // JSON
43
43
  const jsonPath = f.path.replace(/dist[\/\\]css/, "dist/generated/assets").replace(".css", ".css.json");
44
44
  await mkdir(path.dirname(jsonPath), {recursive: true});
45
- const data = {
46
- packageName: packageJSON.name,
47
- fileName: jsonPath.substr(jsonPath.lastIndexOf("themes")),
48
- content: newText,
49
- };
50
- writeFileIfChanged(jsonPath, JSON.stringify({_: data}));
45
+ writeFileIfChanged(jsonPath, JSON.stringify(newText));
51
46
 
52
47
  // JS/TS
53
48
  const jsPath = f.path.replace(/dist[\/\\]css/, "src/generated/").replace(".css", extension);
54
- const jsContent = getFileContent(tsMode, jsPath, packageJSON.name, "\`" + newText + "\`");
49
+ const jsContent = getFileContent(packageJSON.name, "\`" + newText + "\`");
55
50
  writeFileIfChanged(jsPath, jsContent);
56
51
  });
57
52
  })
@@ -47,30 +47,10 @@ registerThemePropertiesLoader("${packageName}", "${DEFAULT_THEME}", async () =>
47
47
  `;
48
48
  };
49
49
 
50
- const getFileContent = (tsMode, targetFile, packageName, css, includeDefaultTheme) => {
51
- if (tsMode) {
52
- return getTSContent(targetFile, packageName, css, includeDefaultTheme);
53
- }
54
-
55
- return getJSContent(targetFile, packageName, css, includeDefaultTheme);
56
- }
57
-
58
- const getTSContent = (targetFile, packageName, css, includeDefaultTheme) => {
59
- const typeImport = "import type { StyleData } from \"@ui5/webcomponents-base/dist/types.js\";"
50
+ const getFileContent = (packageName, css, includeDefaultTheme) => {
60
51
  const defaultTheme = includeDefaultTheme ? getDefaultThemeCode(packageName) : "";
61
-
62
- // tabs are intentionally mixed to have proper identation in the produced file
63
- return `${typeImport}
64
- ${defaultTheme}
65
- const styleData: StyleData = {packageName:"${packageName}",fileName:"${targetFile.substr(targetFile.lastIndexOf("themes"))}",content:${css}};
66
- export default styleData;
67
- `;
52
+ return `${defaultTheme}export default ${css.trim()}`
68
53
  }
69
54
 
70
- const getJSContent = (targetFile, packageName, css, includeDefaultTheme) => {
71
- const defaultTheme = includeDefaultTheme ? getDefaultThemeCode(packageName) : "";
72
-
73
- return `${defaultTheme}export default {packageName:"${packageName}",fileName:"${targetFile.substr(targetFile.lastIndexOf("themes"))}",content:${css}}`
74
- }
75
55
 
76
56
  export { writeFileIfChanged, stripThemingBaseContent, getFileContent}
@@ -1,23 +1,17 @@
1
- const virtualIndexPlugin = async () => {
2
- const path = await import("path");
3
- const { globby } = await import("globby");
4
- const files = await globby(["test/pages/**/*.html", "packages/*/test/pages/**/*.html"]);
5
-
6
- const pagesPerFolder = {};
7
- files.forEach(file => {
8
- let folder = pagesPerFolder[path.dirname(file)] = pagesPerFolder[path.dirname(file)] || [];
9
- folder.push(path.basename(file));
10
- });
1
+ const virtualIndexPlugin = () => {
2
+ return {
3
+ name: 'virtual-index-html',
4
+ async config() {
5
+ const path = (await import("path")).default;
6
+ const globby = (await import("globby")).globby;
7
+ const files = await globby(["test/pages/**/*.html", "packages/*/test/pages/**/*.html"]);
11
8
 
12
- const rollupInput = {};
9
+ const rollupInput = {};
13
10
 
14
- files.forEach(file => {
15
- rollupInput[file] = path.resolve(process.cwd(), file);
16
- })
11
+ files.forEach(file => {
12
+ rollupInput[file] = path.resolve(process.cwd(), file);
13
+ });
17
14
 
18
- return {
19
- name: 'virtual-index-html',
20
- config() {
21
15
  return {
22
16
  build: {
23
17
  rollupOptions: {
@@ -26,7 +20,17 @@ const virtualIndexPlugin = async () => {
26
20
  }
27
21
  }
28
22
  },
29
- configureServer(server) {
23
+ async configureServer(server) {
24
+ const path = (await import("path")).default;
25
+ const globby = (await import("globby")).globby;
26
+ const files = await globby(["test/pages/**/*.html", "packages/*/test/pages/**/*.html"]);
27
+
28
+ const pagesPerFolder = {};
29
+ files.forEach(file => {
30
+ let folder = pagesPerFolder[path.dirname(file)] = pagesPerFolder[path.dirname(file)] || [];
31
+ folder.push(path.basename(file));
32
+ });
33
+
30
34
  server.middlewares.use((req, res, next) => {
31
35
  if (req.url === "/") {
32
36
  const folders = Object.keys(pagesPerFolder);
@@ -37,8 +41,8 @@ const virtualIndexPlugin = async () => {
37
41
  const pages = pagesPerFolder[folder];
38
42
  return `<h1>${folder}</h1>
39
43
  ${pages.map(page => {
40
- return `<li><a href='${folder}/${page}'>${page}</a></li>`
41
- }).join("")}
44
+ return `<li><a href='${folder}/${page}'>${page}</a></li>`
45
+ }).join("")}
42
46
  `
43
47
  }).join("")}`);
44
48
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ui5/webcomponents-tools",
3
- "version": "0.0.0-f42e7c18c",
3
+ "version": "0.0.0-f6676abdd",
4
4
  "description": "UI5 Web Components: webcomponents.tools",
5
5
  "author": "SAP SE (https://www.sap.com)",
6
6
  "license": "Apache-2.0",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@custom-elements-manifest/analyzer": "^0.8.4",
26
+ "@cypress/code-coverage": "^3.13.10",
26
27
  "@typescript-eslint/eslint-plugin": "^6.9.0",
27
28
  "@typescript-eslint/parser": "^6.9.0",
28
29
  "@wdio/cli": "^7.19.7",
@@ -48,7 +49,8 @@
48
49
  "eslint": "^7.22.0",
49
50
  "eslint-config-airbnb-base": "^14.2.1",
50
51
  "eslint-plugin-cypress": "^3.4.0",
51
- "eslint-plugin-import": "^2.22.1",
52
+ "eslint-plugin-import": "^2.31.0",
53
+ "eslint-plugin-jsx-no-leaked-values": "^0.1.24",
52
54
  "esprima": "^4.0.1",
53
55
  "getopts": "^2.3.0",
54
56
  "glob": "^7.1.6",
@@ -62,6 +64,7 @@
62
64
  "postcss": "^8.4.5",
63
65
  "postcss-cli": "^9.1.0",
64
66
  "postcss-selector-parser": "^6.0.10",
67
+ "preact": "^10.25.4",
65
68
  "prompts": "^2.4.2",
66
69
  "properties-reader": "^2.2.0",
67
70
  "recursive-readdir": "^2.2.2",
@@ -69,6 +72,7 @@
69
72
  "rimraf": "^3.0.2",
70
73
  "slash": "3.0.0",
71
74
  "vite": "^5.4.8",
75
+ "vite-plugin-istanbul": "^6.0.2",
72
76
  "wdio-chromedriver-service": "^7.3.2"
73
77
  },
74
78
  "peerDependencies": {
@@ -81,9 +85,9 @@
81
85
  }
82
86
  },
83
87
  "devDependencies": {
84
- "cypress-ct-lit": "^0.4.0",
88
+ "@cypress/mount-utils": "^4.1.2",
85
89
  "cypress-real-events": "^1.12.0",
86
- "esbuild": "^0.19.9",
90
+ "esbuild": "^0.25.0",
87
91
  "yargs": "^17.5.1"
88
92
  }
89
93
  }
package/tsconfig.json CHANGED
@@ -12,5 +12,7 @@
12
12
  "inlineSources": true,
13
13
  "strict": true,
14
14
  "moduleResolution": "node",
15
+ "jsx": "react-jsx",
16
+ "jsxImportSource": "@ui5/webcomponents-base",
15
17
  }
16
18
  }