mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-17 17:40:31 +08:00
test(runtime): add hidden trait test
* main: feat: add the `requestFullscreen` method to the iframe component feat: add the iframe component
This commit is contained in:
parent
bf54a0c4e9
commit
da066b4519
@ -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": []
|
||||
}
|
||||
|
@ -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(<App options={SingleComponentSchema} />);
|
||||
|
||||
// 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(<App options={HiddenTraitSchema} />);
|
||||
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');
|
||||
|
@ -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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -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}}`);
|
||||
|
@ -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<HTMLDivElement, ImplWrapperProps>(
|
||||
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<HTMLDivElement, ImplWrapperProps
|
||||
executeTrait(
|
||||
t,
|
||||
stateManager.deepEval(t.properties, {
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
fallbackWhenError: () => undefined,
|
||||
})
|
||||
)
|
||||
@ -67,8 +59,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
});
|
||||
},
|
||||
{
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
fallbackWhenError: () => undefined,
|
||||
}
|
||||
);
|
||||
@ -79,7 +70,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
// because mergeState will be called during the first render of component, and state will change
|
||||
setTraitResults(c.traits.map((trait, i) => 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<string, string>['props'] = useMemo(() => {
|
||||
@ -106,8 +97,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
return merge(
|
||||
stateManager.deepEval(c.properties, {
|
||||
fallbackWhenError: () => undefined,
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
}),
|
||||
propsFromTraits
|
||||
);
|
||||
@ -119,17 +109,13 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
({ result: newResult }: any) => {
|
||||
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());
|
||||
|
40
packages/runtime/src/components/test/Input.tsx
Normal file
40
packages/runtime/src/components/test/Input.tsx
Normal file
@ -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 (
|
||||
<input data-testid={testId} value={value} onChange={e => setValue(e.target.value)} />
|
||||
);
|
||||
});
|
@ -34,6 +34,7 @@ export default implementRuntimeComponent({
|
||||
},
|
||||
})(({ testId, text }) => {
|
||||
renderTimesMap[testId] = (renderTimesMap[testId] || 0) + 1;
|
||||
console.log('testId', renderTimesMap);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
@ -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);
|
||||
|
@ -25,8 +25,6 @@ type EvalOptions = {
|
||||
scopeObject?: Record<string, any>;
|
||||
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<string, unknown>;
|
||||
|
||||
// when ignoreEvalError is true, the eval process will continue after error happens in nests expression.
|
||||
noConsoleError = false;
|
||||
|
||||
constructor(dependencies: Record<string, unknown> = {}) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user