diff --git a/examples/box/index.json b/examples/box/index.json index bf83cda4..c05192b5 100644 --- a/examples/box/index.json +++ b/examples/box/index.json @@ -1,15 +1,18 @@ { "app": { - "version": "example/v1", - "metadata": { "name": "box", "description": "box" }, + "version": "sunmao/v1", + "kind": "Application", + "metadata": { + "name": "some App" + }, "spec": { "components": [ { - "id": "tester5", + "id": "tester", "type": "test/v1/tester", "properties": { - "testId": "single", - "text": "text" + "testId": "tester", + "text": "2333" }, "traits": [] } diff --git a/packages/runtime/__tests__/ImplWrapper/ImplWrapper.spec.tsx b/packages/runtime/__tests__/ImplWrapper/ImplWrapper.spec.tsx index f0e0a3b5..3cad1a25 100644 --- a/packages/runtime/__tests__/ImplWrapper/ImplWrapper.spec.tsx +++ b/packages/runtime/__tests__/ImplWrapper/ImplWrapper.spec.tsx @@ -3,7 +3,13 @@ import { render, fireEvent, screen, waitFor, act } from '@testing-library/react' import produce from 'immer'; import { times } from 'lodash'; import { initSunmaoUI } from '../../src'; -import { SingleComponentSchema, ComponentSchemaChangeSchema } from './mockSchema.spec'; +import { + SingleComponentSchema, + ComponentSchemaChangeSchema, + HiddenTraitSchema, +} from './mockSchema.spec'; + +const SingleComponentRenderTimes = '2'; describe('single component condition', () => { it('only render one time', () => { @@ -11,7 +17,7 @@ describe('single component condition', () => { const { unmount } = render(); // simple component will render 2 times, because it have to eval trait and properties twice - expect(screen.getByTestId('single')?.textContent).toEqual('2'); + expect(screen.getByTestId('single')?.textContent).toEqual(SingleComponentRenderTimes); expect(screen.getByTestId('single-destroy')?.textContent).toEqual('0'); unmount(); }); @@ -38,6 +44,19 @@ describe('after the schema changes', () => { }); }); +describe('hidden trait condition', () => { + it('the hidden component should not merge state in store', () => { + const { App, stateManager } = initSunmaoUI(); + stateManager.noConsoleError = true; + const { unmount } = render(); + expect(screen.getByTestId('tester')?.textContent).toEqual(SingleComponentRenderTimes); + expect(screen.getByTestId('tester-text')?.textContent).toEqual(''); + expect(stateManager.store['input1']).toBeUndefined(); + + unmount(); + }); +}); + // expect(screen.getByTestId('tester1-text')?.textContent).toEqual('0'); // expect(screen.getByTestId('tester1')?.textContent).toEqual('3'); diff --git a/packages/runtime/__tests__/ImplWrapper/mockSchema.spec.ts b/packages/runtime/__tests__/ImplWrapper/mockSchema.spec.ts index 706be0c0..895e7eb2 100644 --- a/packages/runtime/__tests__/ImplWrapper/mockSchema.spec.ts +++ b/packages/runtime/__tests__/ImplWrapper/mockSchema.spec.ts @@ -118,3 +118,40 @@ export const MockSchema: Application = { ], }, }; + +export const HiddenTraitSchema: Application = { + version: 'sunmao/v1', + kind: 'Application', + metadata: { + name: 'some App', + }, + spec: { + components: [ + { + id: 'input1', + type: 'test/v1/input', + properties: { + testId: '', + defaultValue: 'foo', + }, + traits: [ + { + type: 'core/v1/hidden', + properties: { + hidden: true, + }, + }, + ], + }, + { + id: 'tester', + type: 'test/v1/tester', + properties: { + testId: 'tester', + text: '{{input1.value}}', + }, + traits: [], + }, + ], + }, +}; diff --git a/packages/runtime/__tests__/expression.spec.ts b/packages/runtime/__tests__/expression.spec.ts index 119e6bd5..92d72fe6 100644 --- a/packages/runtime/__tests__/expression.spec.ts +++ b/packages/runtime/__tests__/expression.spec.ts @@ -24,8 +24,9 @@ describe('evalExpression function', () => { }, }; const stateManager = new StateManager(); + stateManager.noConsoleError = true; it('can eval {{}} expression', () => { - const evalOptions = { evalListItem: false, scopeObject: scope, noConsoleError: true }; + const evalOptions = { evalListItem: false, scopeObject: scope }; expect(stateManager.maskedEval('value', evalOptions)).toEqual('value'); expect(stateManager.maskedEval('{{true}}', evalOptions)).toEqual(true); @@ -80,7 +81,6 @@ describe('evalExpression function', () => { stateManager.maskedEval('{{value}}', { scopeObject: { override: 'foo' }, overrideScope: true, - noConsoleError: true, }) ).toBeInstanceOf(ExpressionError); expect( @@ -95,7 +95,6 @@ describe('evalExpression function', () => { expect( stateManager.maskedEval('{{wrongExp}}', { fallbackWhenError: exp => exp, - noConsoleError: true, }) ).toEqual('{{wrongExp}}'); }); @@ -107,7 +106,6 @@ describe('evalExpression function', () => { $moduleId: 'myModule', text: 'hello', }, - noConsoleError: true, ignoreEvalError: true, }) ).toEqual(`hello {{myModule__state0.value}}`); diff --git a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx index 2242cf68..fe07fcf5 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx +++ b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx @@ -8,23 +8,16 @@ import { getSlotElements } from './hooks/useSlotChildren'; import { useGlobalHandlerMap } from './hooks/useGlobalHandlerMap'; import { useEleRef } from './hooks/useEleMap'; import { useGridLayout } from './hooks/useGridLayout'; -import { initStateAndMethod } from '../../../utils/initStateAndMethod'; export const ImplWrapperMain = React.forwardRef( function ImplWrapperMain(props, ref) { - const { component: c, children, slotProps, evalListItem } = props; + const { component: c, children } = props; const { registry, stateManager } = props.services; const Impl = registry.getComponent(c.parsedType.version, c.parsedType.name).impl; useGlobalHandlerMap(props); - // This code is to init dynamic generated components - // because they have not be initialized before - if (!stateManager.store[c.id]) { - initStateAndMethod(registry, stateManager, [c]); - } - const { eleRef, onRef } = useEleRef(props); const { mergeState, subscribeMethods, executeTrait } = useRuntimeFunctions(props); @@ -35,8 +28,7 @@ export const ImplWrapperMain = React.forwardRef undefined, }) ) @@ -67,8 +59,7 @@ export const ImplWrapperMain = React.forwardRef undefined, } ); @@ -79,7 +70,7 @@ export const ImplWrapperMain = React.forwardRef executeTrait(trait, properties[i]))); return () => stops.forEach(s => s()); - }, [c.id, c.traits, executeTrait, stateManager, slotProps, evalListItem]); + }, [c.id, c.traits, executeTrait, stateManager, props.slotProps]); // reduce traitResults const propsFromTraits: TraitResult['props'] = useMemo(() => { @@ -106,8 +97,7 @@ export const ImplWrapperMain = React.forwardRef undefined, - evalListItem, - scopeObject: { $slot: slotProps }, + scopeObject: { $slot: props.slotProps }, }), propsFromTraits ); @@ -119,17 +109,13 @@ export const ImplWrapperMain = React.forwardRef { setEvaledComponentProperties({ ...newResult }); }, - { - evalListItem, - fallbackWhenError: () => undefined, - scopeObject: { $slot: slotProps }, - } + { fallbackWhenError: () => undefined, scopeObject: { $slot: props.slotProps } } ); // must keep this line, reason is the same as above setEvaledComponentProperties({ ...result }); return stop; - }, [c.properties, stateManager, slotProps]); + }, [c.properties, stateManager, props.slotProps]); useEffect(() => { const clearFunctions = propsFromTraits?.componentDidMount?.map(e => e()); diff --git a/packages/runtime/src/components/test/Input.tsx b/packages/runtime/src/components/test/Input.tsx new file mode 100644 index 00000000..63f22ab1 --- /dev/null +++ b/packages/runtime/src/components/test/Input.tsx @@ -0,0 +1,40 @@ +import { implementRuntimeComponent } from '../../utils/buildKit'; +import { Type } from '@sinclair/typebox'; +import { useState , useEffect } from 'react'; + +export default implementRuntimeComponent({ + version: 'test/v1', + metadata: { + name: 'input', + displayName: 'Input', + description: 'for test', + isDraggable: false, + isResizable: false, + exampleProperties: {}, + exampleSize: [1, 1], + annotations: { + category: 'Advance', + }, + }, + spec: { + properties: Type.Object({ + testId: Type.String(), + defaultValue: Type.String(), + }), + state: Type.Object({ + value: Type.String(), + }), + methods: {}, + slots: {}, + styleSlots: [], + events: [], + }, +})(({ testId, defaultValue, mergeState }) => { + const [value, setValue] = useState(defaultValue || ''); + useEffect(() => { + mergeState({ value }); + }); + return ( + setValue(e.target.value)} /> + ); +}); diff --git a/packages/runtime/src/components/test/Tester.tsx b/packages/runtime/src/components/test/Tester.tsx index 358f4944..529cee61 100644 --- a/packages/runtime/src/components/test/Tester.tsx +++ b/packages/runtime/src/components/test/Tester.tsx @@ -34,6 +34,7 @@ export default implementRuntimeComponent({ }, })(({ testId, text }) => { renderTimesMap[testId] = (renderTimesMap[testId] || 0) + 1; + console.log('testId', renderTimesMap); useEffect(() => { return () => { diff --git a/packages/runtime/src/services/Registry.tsx b/packages/runtime/src/services/Registry.tsx index 41ec7dfd..f22ddaca 100644 --- a/packages/runtime/src/services/Registry.tsx +++ b/packages/runtime/src/services/Registry.tsx @@ -14,6 +14,7 @@ import CoreIframe from '../components/core/Iframe'; // test import TestButton from '../components/test/Button'; import TestTester from '../components/test/Tester'; +import TestInput from '../components/test/Input'; // traits import CoreArrayState from '../traits/core/ArrayState'; @@ -253,6 +254,7 @@ export function initRegistry( registry.registerComponent(TestTester); registry.registerComponent(TestButton); + registry.registerComponent(TestInput); registry.registerTrait(CoreState); registry.registerTrait(CoreArrayState); diff --git a/packages/runtime/src/services/StateManager.ts b/packages/runtime/src/services/StateManager.ts index c10987b6..118689c3 100644 --- a/packages/runtime/src/services/StateManager.ts +++ b/packages/runtime/src/services/StateManager.ts @@ -25,8 +25,6 @@ type EvalOptions = { scopeObject?: Record; overrideScope?: boolean; fallbackWhenError?: (exp: string) => any; - noConsoleError?: boolean; - // when ignoreEvalError is true, the eval process will continue after error happens in nests expression. ignoreEvalError?: boolean; }; @@ -50,6 +48,9 @@ export class StateManager { dependencies: Record; + // when ignoreEvalError is true, the eval process will continue after error happens in nests expression. + noConsoleError = false; + constructor(dependencies: Record = {}) { this.dependencies = { ...DefaultDependencies, ...dependencies }; } @@ -93,7 +94,7 @@ export class StateManager { }; maskedEval(raw: string, options: EvalOptions = {}): unknown | ExpressionError { - const { evalListItem = false, fallbackWhenError, noConsoleError } = options; + const { evalListItem = false, fallbackWhenError } = options; let result: unknown[] = []; try { @@ -122,7 +123,7 @@ export class StateManager { if (error instanceof Error) { const expressionError = new ExpressionError(error.message); - if (!noConsoleError) { + if (!this.noConsoleError) { consoleError(ConsoleType.Expression, '', expressionError.message); }