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:
Bowen Tan 2022-06-09 11:07:02 +08:00
parent bf54a0c4e9
commit da066b4519
9 changed files with 123 additions and 36 deletions

View File

@ -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": []
}

View File

@ -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');

View File

@ -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: [],
},
],
},
};

View File

@ -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}}`);

View File

@ -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());

View 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)} />
);
});

View File

@ -34,6 +34,7 @@ export default implementRuntimeComponent({
},
})(({ testId, text }) => {
renderTimesMap[testId] = (renderTimesMap[testId] || 0) + 1;
console.log('testId', renderTimesMap);
useEffect(() => {
return () => {

View File

@ -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);

View File

@ -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);
}