From 7ca05466bce2fc608966301f9e4d3a7a6aad4324 Mon Sep 17 00:00:00 2001 From: Christian Westgaard Date: Tue, 5 Nov 2024 14:16:34 +0100 Subject: [PATCH] wip BasePart --- jest.config.ts | 1 + package-lock.json | 77 ++++++---- package.json | 4 + src/ComponentRegistry.tsx | 72 +++++---- src/ComponentRegistry/BasePart.tsx | 33 ++++ src/RichText/replaceMacro.tsx | 5 +- src/constants.ts | 8 + src/index.ts | 5 + src/processComponents.ts | 143 +++++++++--------- src/types/index.ts | 24 ++- test/ComponentRegistry/BasePart.test.tsx | 112 ++++++++++++++ .../ComponentRegistry.test.tsx | 41 +++++ test/ComponentRegistry/ExamplePart.tsx | 36 +++++ test/RichText.ComponentRegistry.test.tsx | 4 +- .../processComponents.test.tsx | 10 +- test/replaceMacroComments.test.ts | 14 +- test/tsconfig.json | 7 + tsconfig.json | 63 ++++---- tsup.config.ts | 40 +++++ 19 files changed, 528 insertions(+), 171 deletions(-) create mode 100644 src/ComponentRegistry/BasePart.tsx create mode 100644 test/ComponentRegistry/BasePart.test.tsx create mode 100644 test/ComponentRegistry/ComponentRegistry.test.tsx create mode 100644 test/ComponentRegistry/ExamplePart.tsx diff --git a/jest.config.ts b/jest.config.ts index a26ebb6..0c69a57 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,6 +11,7 @@ export default { `!src/${AND_BELOW}/${DECLARATION_FILES}` ], // coverageProvider: 'v8', + silent: true, // All below console.error testEnvironment: 'jsdom', testMatch: [ `/test/${AND_BELOW}/${TEST_FILES}`, diff --git a/package-lock.json b/package-lock.json index 4be6297..c879cbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { + "@enonic/js-utils": "^1.8.0", "domelementtype": "^2.3.0", "html-react-parser": "^5.1.10", "uri-js": "^4.4.1" @@ -29,6 +30,7 @@ "del-cli": "^6.0.0", "diffable-html": "^5.0.0", "domhandler": "^5.0.3", + "esbuild-plugin-globals": "^0.2.0", "glob": "^11.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -42,6 +44,8 @@ }, "peerDependencies": { "@enonic-types/core": "^7", + "@enonic-types/lib-portal": "^7", + "@enonic-types/lib-schema": "^7", "prop-types": "*", "react": "*" } @@ -661,8 +665,7 @@ "node_modules/@enonic-types/core": { "version": "7.14.4", "resolved": "https://registry.npmjs.org/@enonic-types/core/-/core-7.14.4.tgz", - "integrity": "sha512-AzMe/DixiaNH8X3OEvy0AREwr3GcmmcQ5lQ3k8AQWK4rQVKmSVatuNslkQFvzakjc7BOcTHGSBxr5lhN9+qD8g==", - "dev": true + "integrity": "sha512-AzMe/DixiaNH8X3OEvy0AREwr3GcmmcQ5lQ3k8AQWK4rQVKmSVatuNslkQFvzakjc7BOcTHGSBxr5lhN9+qD8g==" }, "node_modules/@enonic-types/global": { "version": "7.14.3", @@ -708,6 +711,22 @@ "@enonic-types/core": "7.14.4" } }, + "node_modules/@enonic-types/lib-schema": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@enonic-types/lib-schema/-/lib-schema-7.14.4.tgz", + "integrity": "sha512-znXDWn0C/w4Dq8r1voe7yoMGKlVX+8Fr5+0r9H6M7Bdt7bAEe1fegqT+XW/GaBr0eUyh5ParPto03alLlmulyA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@enonic-types/core": "7.14.4" + } + }, + "node_modules/@enonic/js-utils": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@enonic/js-utils/-/js-utils-1.8.0.tgz", + "integrity": "sha512-4Zmj3YhvAwdiIpxeFgHZEOXKnUytzEn1fJRnPw3ynxdQLkduwa/SCIo8kBdtunDVrFKuYe2pBM8RaR1xvBaYzw==", + "license": "Apache-2.0" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", @@ -3530,10 +3549,16 @@ "dev": true }, "node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/error-ex": { "version": "1.3.2", @@ -3583,6 +3608,16 @@ "@esbuild/win32-x64": "0.24.0" } }, + "node_modules/esbuild-plugin-globals": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/esbuild-plugin-globals/-/esbuild-plugin-globals-0.2.0.tgz", + "integrity": "sha512-y+6utQVWrETQWs0J8EGLV5gEOP59mmjX+fKWoQHn4TYwFMaj0FxQYflc566tHuokBCzl+uNW2iIlM1o1jfNy6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=7" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -4038,17 +4073,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/html-dom-parser/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/html-dom-parser/node_modules/htmlparser2": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", @@ -4134,6 +4158,13 @@ "domelementtype": "1" } }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -6133,18 +6164,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", diff --git a/package.json b/package.json index d8cb54c..7dcf1e0 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "del-cli": "^6.0.0", "diffable-html": "^5.0.0", "domhandler": "^5.0.3", + "esbuild-plugin-globals": "^0.2.0", "glob": "^11.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -68,6 +69,8 @@ "name": "@enonic/react-components", "peerDependencies": { "@enonic-types/core": "^7", + "@enonic-types/lib-portal": "^7", + "@enonic-types/lib-schema": "^7", "prop-types": "*", "react": "*" }, @@ -96,6 +99,7 @@ "type": "module", "types": "dist/index.d.ts", "dependencies": { + "@enonic/js-utils": "^1.8.0", "domelementtype": "^2.3.0", "html-react-parser": "^5.1.10", "uri-js": "^4.4.1" diff --git a/src/ComponentRegistry.tsx b/src/ComponentRegistry.tsx index d5418a5..4a47154 100644 --- a/src/ComponentRegistry.tsx +++ b/src/ComponentRegistry.tsx @@ -1,48 +1,64 @@ -import type {ComponentRegistry as ComponentRegistryInterface} from './types'; +import type { + ComponentDefinition, + ComponentDefinitionParams, + ComponentDictionary, + ComponentRegistry as ComponentRegistryInterface +} from './types'; // import {XP_COMPONENT_TYPE} from './constants'; export class ComponentRegistry implements ComponentRegistryInterface { - // private pages: ComponentDictionary = {}; - // private parts: ComponentDictionary = {}; - // private layouts: ComponentDictionary = {}; - private _macros: {[macroName: string]: React.FunctionComponent} = {}; + private _pages: ComponentDictionary = {}; + private _parts: ComponentDictionary = {}; + private _layouts: ComponentDictionary = {}; + private _macros: ComponentDictionary = {}; - public addMacro(name: string, component: React.FunctionComponent): React.FunctionComponent { - this._macros[name] = component; - return this._macros[name]; + public addMacro(name: string, obj: ComponentDefinitionParams): void { + this._macros[name] = obj as ComponentDefinition<{}>; } - // public addLayout(name: string, obj: ComponentDefinitionParams): void { - // return ComponentRegistry.addType('layout', name, obj); - // } + public addLayout(name: string, obj: ComponentDefinitionParams): void { + this._layouts[name] = obj as ComponentDefinition<{}>; + } - // public addPage(name: string, obj: ComponentDefinitionParams): void { - // return ComponentRegistry.addType('page', name, obj); - // } + public addPage(name: string, obj: ComponentDefinitionParams): void { + this._pages[name] = obj as ComponentDefinition<{}>; + } - // public addPart(name: string, obj: ComponentDefinitionParams): void { - // return ComponentRegistry.addType('part', name, obj); - // } + public addPart(name: string, obj: ComponentDefinitionParams): void { + this._parts[name] = obj as ComponentDefinition<{}>; + } - public getMacro(name: string): React.FunctionComponent | undefined { - return this._macros[name]; + public getLayout(name: string): ComponentDefinition | undefined { + return this._layouts[name] as ComponentDefinition; } - // public getPage(name: string): ComponentDefinition | undefined { - // return ComponentRegistry.getType('page', name); - // } + public getMacro(name: string): ComponentDefinition | undefined { + return this._macros[name] as ComponentDefinition; + } - // public getPart(name: string): ComponentDefinition | undefined { - // return ComponentRegistry.getType('part', name); - // } + public getPage(name: string): ComponentDefinition | undefined { + return this._pages[name] as ComponentDefinition; + } - // public getLayout(name: string): ComponentDefinition | undefined { - // return ComponentRegistry.getType('layout', name); - // } + public getPart(name: string): ComponentDefinition | undefined { + return this._parts[name] as ComponentDefinition; + } public hasMacro(name: string): boolean { return this._macros[name] !== undefined; } + + public hasLayout(name: string): boolean { + return this._layouts[name] !== undefined; + } + + public hasPage(name: string): boolean { + return this._pages[name] !== undefined; + } + + public hasPart(name: string): boolean { + return this._parts[name] !== undefined; + } } diff --git a/src/ComponentRegistry/BasePart.tsx b/src/ComponentRegistry/BasePart.tsx new file mode 100644 index 0000000..c1653bf --- /dev/null +++ b/src/ComponentRegistry/BasePart.tsx @@ -0,0 +1,33 @@ +import type {PartComponent} from '@enonic-types/core'; + +import type {ComponentRegistry} from '../types'; + + +export function BasePart({ + component, + componentRegistry +}: { + component: PartComponent + componentRegistry: ComponentRegistry +}) { + // console.debug('BasePart component', component); + + const { + config: props, + descriptor + } = component; + // console.debug('BasePart descriptor', descriptor); + + const partDefinition = componentRegistry.getPart(descriptor); + if (!partDefinition) { + throw new Error(`Part definition not found for descriptor: ${descriptor}`); + // TODO return ErrorBoundary instead of throwing. + } + const {View} = partDefinition; + if (!View) { + throw new Error(`Part view not found for descriptor: ${descriptor}`); + // TODO return ErrorBoundary instead of throwing. + } + props.componentRegistry = componentRegistry; + return (); +} diff --git a/src/RichText/replaceMacro.tsx b/src/RichText/replaceMacro.tsx index 0d90d41..3c2febb 100644 --- a/src/RichText/replaceMacro.tsx +++ b/src/RichText/replaceMacro.tsx @@ -43,8 +43,9 @@ export function replaceMacro>({ const config = configs[name]; if (componentRegistry) { - const MacroComponent = componentRegistry.getMacro(name); - if (MacroComponent) { + const MacroComponentDefinition = componentRegistry.getMacro(name); + if (MacroComponentDefinition) { + const MacroComponent = MacroComponentDefinition.View; return ( ); diff --git a/src/constants.ts b/src/constants.ts index 0fab9f7..0805fe7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,3 +5,11 @@ export const MACRO_TAG = 'editor-macro'; export const IMG_ATTR = 'data-image-ref'; export const LINK_ATTR = 'data-link-ref'; export const MACRO_ATTR = 'data-macro-ref'; + +export enum XP_COMPONENT_TYPE { + FRAGMENT = 'fragment', + LAYOUT = 'layout', + PAGE = 'page', + PART = 'part', + TEXT = 'text', +} diff --git a/src/index.ts b/src/index.ts index b4fbd97..e08f605 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,6 +36,11 @@ export { Regions, }; export { ComponentRegistry } from './ComponentRegistry'; + +// Exporting processComponents here is a bad idea, because it's should be used in Enonic XP nashorn. +// Nashorn throws "Invalid hex digit" because entities contains Uint16Array. +// export { processComponents } from './processComponents'; + export { replaceMacroComments } from './replaceMacroComments'; export { RichText } from './RichText'; export { cssToReactStyle } from './RichText/cssToReactStyle'; diff --git a/src/processComponents.ts b/src/processComponents.ts index b5be16d..6a4cce3 100644 --- a/src/processComponents.ts +++ b/src/processComponents.ts @@ -40,19 +40,19 @@ export type NestedPartial = { [K in keyof T]?: T[K] extends object ? NestedPartial : T[K]; }; export interface GetComponentReturnType { - componentPath: string - config: Record - description?: string - descriptionI18nKey?: string - displayName: string - displayNameI18nKey: string - form: NestedPartial[] - key: string - modifiedTime: string - regions?: string[] - resource: string - type: 'PART' | 'LAYOUT' | 'PAGE' -} + componentPath: string; + config: Record; + description?: string; + descriptionI18nKey?: string; + displayName: string; + displayNameI18nKey: string; + form: NestedPartial[]; + key: string; + modifiedTime: string; + regions?: string[]; + resource: string; + type: 'PART' | 'LAYOUT' | 'PAGE'; +}; type GetComponent = (params: GetDynamicComponentParams) => GetComponentReturnType; @@ -67,10 +67,10 @@ function findHtmlAreasInFormItemArray({ htmlAreas, // get modified listSchemas, }: { - ancestor?: string - form: NestedPartial[] - htmlAreas: string[] - listSchemas: ListSchemas + ancestor?: string; + form: NestedPartial[]; + htmlAreas: string[]; + listSchemas: ListSchemas; }) { for (let i = 0; i < form.length; i++) { const formItem = form[i]; @@ -166,9 +166,9 @@ function getHtmlAreas({ form, listSchemas }: { - ancestor?: string - form: NestedPartial[] - listSchemas: ListSchemas + ancestor?: string; + form: NestedPartial[]; + listSchemas: ListSchemas; }): string[] { const htmlAreas: string[] = []; findHtmlAreasInFormItemArray({ @@ -184,17 +184,17 @@ function getHtmlAreas({ function processPart({ component, - getComponent, + getComponentSchema, listSchemas, processHtml, }: { component: PartComponent - getComponent: GetComponent - listSchemas: ListSchemas - processHtml: ProcessHtml + getComponentSchema: GetComponent; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const {descriptor} = component; - const {form} = getComponent({ + const {form} = getComponentSchema({ key: descriptor, type: 'PART', }); @@ -230,17 +230,17 @@ function processPart({ function processWithRegions({ component, form, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }: { - component: LayoutComponent | PageComponent - form: NestedPartial[] - getComponent: GetComponent - getContentByKey: GetContentByKey - listSchemas: ListSchemas - processHtml: ProcessHtml + component: LayoutComponent | PageComponent; + form: NestedPartial[]; + getComponentSchema: GetComponent; + getContentByKey: GetContentByKey; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const htmlAreas = getHtmlAreas({ ancestor: 'config', @@ -278,7 +278,7 @@ function processWithRegions({ const component = components[j]; processedComponent.regions[regionName].components[j] = processComponents({ component, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, @@ -290,26 +290,26 @@ function processWithRegions({ function processLayout({ component, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }: { - component: LayoutComponent - getComponent: GetComponent - getContentByKey: GetContentByKey - listSchemas: ListSchemas - processHtml: ProcessHtml + component: LayoutComponent; + getComponentSchema: GetComponent; + getContentByKey: GetContentByKey; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const {descriptor} = component; - const {form} = getComponent({ + const {form} = getComponentSchema({ key: descriptor, type: 'LAYOUT', }); return processWithRegions({ component, form, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, @@ -318,26 +318,26 @@ function processLayout({ function processPage({ component, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }: { - component: PageComponent - getComponent: GetComponent - getContentByKey: GetContentByKey - listSchemas: ListSchemas - processHtml: ProcessHtml + component: PageComponent; + getComponentSchema: GetComponent; + getContentByKey: GetContentByKey; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const {descriptor} = component; - const {form} = getComponent({ + const {form} = getComponentSchema({ key: descriptor, type: 'PAGE', }); return processWithRegions({ component, form, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, @@ -346,37 +346,30 @@ function processPage({ function processTextComponent({ component, - // getComponent, - // getContentByKey, - // listSchemas, processHtml, }: { component: TextComponent - // getComponent: GetComponent - // getContentByKey: GetContentByKey - // listSchemas: ListSchemas processHtml: ProcessHtml }) { const {text} = component; const processedHtml = processHtml({ value: text }); - const data = replaceMacroComments(processedHtml); - return data; + return replaceMacroComments(processedHtml); } function processFragment({ component, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }: { - component: FragmentComponent - getComponent: GetComponent - getContentByKey: GetContentByKey - listSchemas: ListSchemas - processHtml: ProcessHtml + component: FragmentComponent; + getComponentSchema: GetComponent; + getContentByKey: GetContentByKey; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const { fragment: key, @@ -407,7 +400,7 @@ function processFragment({ if(type === 'part') { return processPart({ component: fragment, - getComponent, + getComponentSchema, listSchemas, processHtml, }); @@ -415,7 +408,7 @@ function processFragment({ if(type === 'layout') { return processLayout({ component: fragment, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, @@ -432,16 +425,16 @@ function processFragment({ export function processComponents({ component, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }: { - component: Component - getComponent: GetComponent - getContentByKey: GetContentByKey - listSchemas: ListSchemas - processHtml: ProcessHtml + component: Component; + getComponentSchema: GetComponent; + getContentByKey: GetContentByKey; + listSchemas: ListSchemas; + processHtml: ProcessHtml; }) { const {type} = component; switch (type) { @@ -449,18 +442,18 @@ export function processComponents({ component, listSchemas, processHtml, - getComponent, + getComponentSchema, }); case 'layout': return processLayout({ component: component as LayoutComponent, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, }); case 'page': return processPage({ component: component as PageComponent, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, @@ -471,7 +464,7 @@ export function processComponents({ }); case 'fragment': return processFragment({ component: component as FragmentComponent, - getComponent, + getComponentSchema, getContentByKey, listSchemas, processHtml, diff --git a/src/types/index.ts b/src/types/index.ts index 45f97a6..059de71 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -10,10 +10,30 @@ import type { } from 'html-react-parser'; import type {ReactNode, JSX} from 'react'; + +export interface ComponentDefinitionParams { + View: React.FunctionComponent +} + +export interface ComponentDefinition { + View: React.FunctionComponent +} + +export type ComponentDictionary = Record>; + export interface ComponentRegistry { - addMacro(name: string, component: React.FunctionComponent): React.FunctionComponent - getMacro(name: string): React.FunctionComponent | undefined + addMacro(name: string, obj: ComponentDefinitionParams): void + addLayout(name: string, obj: ComponentDefinitionParams): void + addPage(name: string, obj: ComponentDefinitionParams): void + addPart(name: string, obj: ComponentDefinitionParams): void + getMacro(name: string): ComponentDefinition | undefined + getLayout(name: string): ComponentDefinition | undefined + getPage(name: string): ComponentDefinition | undefined + getPart(name: string): ComponentDefinition | undefined hasMacro(name: string): boolean + hasLayout(name: string): boolean + hasPage(name: string): boolean + hasPart(name: string): boolean } export type Content< diff --git a/test/ComponentRegistry/BasePart.test.tsx b/test/ComponentRegistry/BasePart.test.tsx new file mode 100644 index 0000000..664e326 --- /dev/null +++ b/test/ComponentRegistry/BasePart.test.tsx @@ -0,0 +1,112 @@ +import type {InfoPanelProps} from '../processComponents/InfoPanel'; + +import { + // beforeAll, + // afterAll, + describe, + expect, + test as it +} from '@jest/globals'; +import {render} from '@testing-library/react' +import toDiffableHtml from 'diffable-html'; +import * as React from 'react'; + +// SRC imports +import {ComponentRegistry} from '../../src/ComponentRegistry'; +import {BasePart} from '../../src/ComponentRegistry/BasePart'; +import {processComponents} from '../../src/processComponents'; + +// TEST imports +import {InfoPanel} from '../processComponents/InfoPanel'; +import { + PART_COMPONENT, + PROCESSED_HTML +} from '../processComponents/data' +import {PART_SCHEMA} from '../processComponents/schema' +import {ExamplePart} from './ExamplePart'; + + +const componentRegistry = new ComponentRegistry; +// const macroName = 'com.enonic.app.react4xp:info'; // NOPE, just 'info' +const macroName = 'info'; +componentRegistry.addMacro(macroName, { + View: InfoPanel +}); +const partName = 'com.enonic.app.react4xp:example'; +componentRegistry.addPart(partName, { + View: ExamplePart +}); + +describe('ComponentRegistry', () => { + it('should be able to render a part component', () => { + const processedComponent = processComponents({ + component: PART_COMPONENT, + getComponentSchema: () => { + return PART_SCHEMA; + }, + // @ts-expect-error + getContentByKey: ({key}) => { + console.debug("getContentByKey:", key); + return {}; + }, + listSchemas: ({ + application, + type + }) => { + console.debug("listSchemas:", application, type); + return []; + }, + processHtml: ({ value }) => { + // console.info("processHtml:", value); + return PROCESSED_HTML; + }, + }); + const element = render().container; + expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` +
+
+
+

+

+ + + + Header + + Text +
+

+
+
+

+

+ + + + Header + + Text +
+

+
+
+

+

+ + + + Header + + Text +
+

+
+
+
+ `)); + // expect(componentRegistry.hasMacro(macroName)).toBe(false); + }); +}); diff --git a/test/ComponentRegistry/ComponentRegistry.test.tsx b/test/ComponentRegistry/ComponentRegistry.test.tsx new file mode 100644 index 0000000..6dbbaaa --- /dev/null +++ b/test/ComponentRegistry/ComponentRegistry.test.tsx @@ -0,0 +1,41 @@ +import type {InfoPanelProps} from '../processComponents/InfoPanel'; + +import { + // beforeAll, + // afterAll, + describe, + expect, + test as it +} from '@jest/globals'; +import {ComponentRegistry} from '../../src/ComponentRegistry'; +import {ExamplePart} from './ExamplePart'; +import {InfoPanel} from '../processComponents/InfoPanel'; + +const componentRegistry = new ComponentRegistry; + +describe('ComponentRegistry', () => { + it('should be able to register macro components', () => { + const macroName = 'com.enonic.app.react4xp:info'; + expect(componentRegistry.hasMacro(macroName)).toBe(false); + expect(componentRegistry.getMacro(macroName)).toBe(undefined); + componentRegistry.addMacro(macroName, { + View: InfoPanel + }); + expect(componentRegistry.hasMacro(macroName)).toBe(true); + expect(componentRegistry.getMacro(macroName)).toEqual({ + View: InfoPanel + }); + }); + it('should be able to register part components', () => { + const partName = 'com.enonic.app.react4xp:example'; + expect(componentRegistry.hasPart(partName)).toBe(false); + expect(componentRegistry.getPart(partName)).toBe(undefined); + componentRegistry.addPart(partName, { + View: ExamplePart + }); + expect(componentRegistry.hasPart(partName)).toBe(true); + expect(componentRegistry.getPart(partName)).toEqual({ + View: ExamplePart + }); + }); +}); diff --git a/test/ComponentRegistry/ExamplePart.tsx b/test/ComponentRegistry/ExamplePart.tsx new file mode 100644 index 0000000..b571a84 --- /dev/null +++ b/test/ComponentRegistry/ExamplePart.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { RichText } from '../../src/RichText'; + +export function ExamplePart(props) { + // console.debug('ExamplePart props', props); + + const { + anHtmlArea, + anItemSet: { + anHtmlArea: anItemHtmlArea, + }, + anOptionSet, + componentRegistry, + } = props; + // console.debug('ExamplePart anOptionSet', anOptionSet); + + const anOptionSetHtmlArea = anOptionSet[1].text.anHtmlArea; + // console.debug('ExamplePart anOptionSetHtmlArea', anOptionSetHtmlArea); + + return ( +
+ + + +
+ ); +} diff --git a/test/RichText.ComponentRegistry.test.tsx b/test/RichText.ComponentRegistry.test.tsx index 92fa734..3eed81f 100644 --- a/test/RichText.ComponentRegistry.test.tsx +++ b/test/RichText.ComponentRegistry.test.tsx @@ -35,7 +35,9 @@ const emptyComponentRegistry = new ComponentRegistry; const componentRegistry = new ComponentRegistry; -componentRegistry.addMacro('info', InfoPanelMacro); +componentRegistry.addMacro('info', { + View: InfoPanelMacro +}); const PROCESSED_HTML = `

Home

diff --git a/test/processComponents/processComponents.test.tsx b/test/processComponents/processComponents.test.tsx index ee31781..83a6512 100644 --- a/test/processComponents/processComponents.test.tsx +++ b/test/processComponents/processComponents.test.tsx @@ -15,7 +15,7 @@ import { } from '@jest/globals'; // import {render} from '@testing-library/react' // import toDiffableHtml from 'diffable-html'; -import {print} from 'q-i'; +// import {print} from 'q-i'; // import React from 'react'; import {ComponentRegistry} from '../../src/ComponentRegistry'; @@ -43,13 +43,15 @@ import { } from './schema'; const componentRegistry = new ComponentRegistry; -componentRegistry.addMacro('info', InfoPanel); +componentRegistry.addMacro('info', { + View: InfoPanel +}); describe('processComponents', () => { it('is able to process anything', () => { const processed = processComponents({ component: PAGE_COMPONENT, - getComponent: ({ + getComponentSchema: ({ // key, type, }) => { @@ -87,7 +89,7 @@ describe('processComponents', () => { return PROCESSED_HTML; }, }); - print(processed, { maxItems: Infinity }); + // print(processed, { maxItems: Infinity }); // expect(toDiffableHtml(element.outerHTML)).toEqual(``); }); }); diff --git a/test/replaceMacroComments.test.ts b/test/replaceMacroComments.test.ts index caf7f7e..ffe2039 100644 --- a/test/replaceMacroComments.test.ts +++ b/test/replaceMacroComments.test.ts @@ -1,9 +1,16 @@ -import {expect, it} from 'bun:test'; +import { + // beforeAll, + // afterAll, + describe, + expect, + test as it +} from '@jest/globals'; import {replaceMacroComments} from '../src/replaceMacroComments'; -it('should replace macro comments', () => { - expect(replaceMacroComments(`

`)) } ] }); + }); }); diff --git a/test/tsconfig.json b/test/tsconfig.json index de151f0..037c385 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -5,5 +5,12 @@ "ES2015", "DOM" ], + "paths": { + "/lib/xp/schema": ["../node_modules/@enonic-types/lib-schema"], + }, + "types": [ + "bun", + "jest" + ] } } diff --git a/tsconfig.json b/tsconfig.json index aa1722c..7d7e5cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,33 +1,42 @@ { // https://www.typescriptlang.org/tsconfig - "compilerOptions": { - "allowUmdGlobalAccess": true, - "declaration": true, - "esModuleInterop": true, - "jsx": "react-jsx", - "lib": [ - "DOM", // NOTE: This ChildNode differs from the one in domhandler npm module. - "ES2015" - ], - "module": "preserve", - "moduleResolution": "bundler", + // Specifies an allowlist of files to include in the program. + // An error occurs if any of the files can’t be found. + // This is useful when you only have a small number of files and don’t need + // to use a glob to reference many files. If you need that then use include. + "files": [ + "src/index.ts" + ], - // Even though the setting disables type checking for d.ts files, - // TypeScript still type checks the code you specifically - // refer to in your application's source code. - "skipLibCheck": true, + "include": [ + "src/**/*.tsx", + "src/**/*.ts" + ], - "strictNullChecks": true, - }, + "compilerOptions": { + "allowUmdGlobalAccess": true, + "declaration": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "lib": [ + "DOM", // NOTE: This ChildNode differs from the one in domhandler npm module. + "ES2015" + ], + "module": "preserve", + "moduleResolution": "bundler", - // Specifies an allowlist of files to include in the program. - // An error occurs if any of the files can’t be found. - // This is useful when you only have a small number of files and don’t need - // to use a glob to reference many files. If you need that then use include. - "files": [ - "src/index.ts" - ], + "paths": { + // This is only needed buildtime + // "html-dom-parser": [ + // // "./node_modules/html-dom-parser/esm/server/html-to-dom.mjs", + // "./node_modules/html-dom-parser/lib/server/html-to-dom.js", + // ], + }, - "include": [ - "src/**/*.ts" - ], + // Even though the setting disables type checking for d.ts files, + // TypeScript still type checks the code you specifically + // refer to in your application's source code. + "skipLibCheck": true, + + "strictNullChecks": true + } } diff --git a/tsup.config.ts b/tsup.config.ts index f5df8be..9593c22 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,6 +1,7 @@ import type { Options } from 'tsup'; +import GlobalsPlugin from 'esbuild-plugin-globals'; import { globSync } from 'glob'; import { defineConfig} from 'tsup'; @@ -16,24 +17,62 @@ const TS_FILES = globSync(`./src/**/*.${GLOB_EXTENSIONS}`, { ignore: [] }).map(dir => dir.replace(/\\/g,'/')); +const external = [ + // 'entities' +]; + +const noExternal = [ + '@enonic/js-utils', + 'domelementtype', + // 'html-dom-parser', + 'html-react-parser', + 'react', // For GlobalsPlugin to work react MUST be listed here (if react under dependencies or peerDependencies) +]; + +const esbuildPlugins = [ + GlobalsPlugin({ + react: 'React', + }), +]; export default defineConfig((options: MyOptions) => { if (Array.isArray(options.format) && options.format[0] === 'cjs') { return { + bundle: true, d: 'dist', dts: false, entry: TS_FILES, + esbuildOptions(options) { + options.alias = { + // "html-dom-parser": "./node_modules/html-dom-parser/esm/server/html-to-dom.mjs", + "html-dom-parser": "./node_modules/html-dom-parser/lib/server/html-to-dom.js", + } + }, + esbuildPlugins, + external, minify: false, + noExternal, platform: 'neutral', target: 'es5', sourcemap: false, + tsconfig: './tsconfig.json', }; } else if (Array.isArray(options.format) && options.format[0] === 'esm') { return { + bundle: true, d: 'dist', dts: false, entry: TS_FILES, + esbuildOptions(options) { + options.alias = { + "html-dom-parser": "./node_modules/html-dom-parser/esm/server/html-to-dom.mjs", + // "html-dom-parser": "./node_modules/html-dom-parser/lib/server/html-to-dom.js", + } + }, + esbuildPlugins, + external, minify: false, + noExternal, outExtension() { return { js: '.mjs' @@ -43,6 +82,7 @@ export default defineConfig((options: MyOptions) => { target: 'es2015', splitting: false, // avoid chunk files sourcemap: false, + tsconfig: './tsconfig.json', }; } throw new Error(`Unsupported format:${options.format}!`)