- new content
+exports[`mini unit render props has new children 1`] = `
+
+ children 01
+ children 02
+
+`;
+
+exports[`onChildrenChange children is array string 1`] = `
+
+ onChildrenChange content 01
+ onChildrenChange content 02
+
+`;
+
+exports[`onPropChange change textNode [key:___condition___] props, but not hidden component 1`] = `
+
+`;
+
+exports[`onPropChange change textNode [key:___condition___] props, hide textNode component 1`] = `
`;
+
+exports[`onPropChange change textNode [key:content], content in this.props but not in leaf.export result 1`] = `
+
+`;
+
+exports[`onPropChange change textNode [key:content], content in this.props but not in leaf.export result 2`] = `
+
+`;
+
+exports[`onVisibleChange visible is false 1`] = `
`;
+
+exports[`onVisibleChange visible is true 1`] = `
+
`;
diff --git a/packages/renderer-core/tests/hoc/leaf.test.tsx b/packages/renderer-core/tests/hoc/leaf.test.tsx
index 3a6e34c91..106e6741a 100644
--- a/packages/renderer-core/tests/hoc/leaf.test.tsx
+++ b/packages/renderer-core/tests/hoc/leaf.test.tsx
@@ -6,12 +6,23 @@ import { leafWrapper } from '../../src/hoc/leaf';
import components from '../utils/components';
import Node from '../utils/node';
+let rerenderCount = 0;
+
+const nodeMap = new Map();
+
+const makeSnapshot = (component) => {
+ let tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+}
+
const baseRenderer: any = {
__debug () {},
__getComponentProps (schema: any) {
return schema.props;
},
- __getSchemaChildrenVirtualDom () {},
+ __getSchemaChildrenVirtualDom (schema: any) {
+ return schema.children;
+ },
context: {
engine: {
createElement,
@@ -19,110 +30,525 @@ const baseRenderer: any = {
},
props: {
__host: {},
- getNode: () => {},
- __container: () => {},
+ getNode: (id) => nodeMap.get(id),
+ __container: {
+ rerender: () => {
+ rerenderCount = 1 + rerenderCount;
+ }
+ },
+ documentId: '01'
}
}
-describe('leafWrapper', () => {
- const Div = leafWrapper(components.Div as any, {
- schema: {
- id: 'div',
+let Div, DivNode, Text, TextNode, component, textSchema, divSchema;
+let id = 0;
+
+beforeEach(() => {
+ textSchema = {
+ id: 'text' + id,
+ props: {
+ content: 'content'
},
+ };
+
+ divSchema = {
+ id: 'div' + id,
+ };
+
+ id++;
+
+ Div = leafWrapper(components.Div as any, {
+ schema: divSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
- const DivNode = new Node({});
- const TextNode = new Node({});
+ DivNode = new Node(divSchema);
+ TextNode = new Node(textSchema);
- const Text = leafWrapper(components.Text as any, {
- schema: {
- id: 'div',
- props: {
- content: 'content'
- }
- },
+ nodeMap.set(divSchema.id, DivNode);
+ nodeMap.set(textSchema.id, TextNode);
+
+ Text = leafWrapper(components.Text as any, {
+ schema: textSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
- const component = renderer.create(
+ component = renderer.create(
// @ts-ignore
);
+});
- it('base', () => {
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
- });
+afterEach(() => {
+ component.unmount(component);
+});
- it('change props', () => {
+describe('onPropChange', () => {
+ it('change textNode [key:content] props', () => {
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ const root = component.root;
+ expect(root.findByType(components.Text).props.content).toEqual('new content')
});
- it('change ___condition___ props', () => {
+ it('change textNode [key:___condition___] props, hide textNode component', () => {
+ // mock leaf?.export result
TextNode.schema.condition = false;
TextNode.emitPropChange({
key: '___condition___',
newValue: false,
} as any);
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ makeSnapshot(component);
});
- it('change ___condition___ props, but not hidden component', () => {
+ it('change textNode [key:___condition___] props, but not hidden component', () => {
TextNode.schema.condition = true;
TextNode.emitPropChange({
key: '___condition___',
newValue: false,
} as any);
- let tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ makeSnapshot(component);
+ });
+
+ it('change textNode [key:content], content in this.props but not in leaf.export result', () => {
+ makeSnapshot(component);
+
+ delete TextNode.schema.props.content;
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: null,
+ } as any, true);
+
+ makeSnapshot(component);
+
+ const root = component.root;
+
+ const TextInst = root.findByType(components.Text);
+
+ expect(TextInst.props.content).toBeNull();
+ });
+
+ it('change textNode [key:___loop___], make rerender', () => {
+ expect(leafWrapper(components.Text as any, {
+ schema: textSchema,
+ baseRenderer,
+ componentInfo: {},
+ scope: {},
+ })).toEqual(Text);
+
+ const nextRerenderCount = rerenderCount + 1;
+
+ TextNode.emitPropChange({
+ key: '___loop___',
+ newValue: 'new content',
+ } as any);
+
+ expect(rerenderCount).toBe(nextRerenderCount);
+ expect(leafWrapper(components.Text as any, {
+ schema: textSchema,
+ baseRenderer,
+ componentInfo: {},
+ scope: {},
+ })).not.toEqual(Text);
+ });
+});
+
+describe('lifecycle', () => {
+ it('props change and make componentWillReceiveProps', () => {
+ makeSnapshot(component);
+
+ // 没有 __tag 标识
+ component.update((
+
+
+
+ ));
+
+ makeSnapshot(component);
+
+ // 有 __tag 标识
+ component.update((
+
+
+
+ ));
+
+ makeSnapshot(component);
+ });
+
+ it('leaf change and make componentWillReceiveProps', () => {
+ const newTextNodeLeaf = new Node(textSchema);
+ component.update((
+
+
+
+ ));
+
+ newTextNodeLeaf.emitPropChange({
+ key: 'content',
+ newValue: 'content new leaf',
+ });
+
+ makeSnapshot(component);
+ });
+});
+
+describe('mini unit render', () => {
+ let miniRenderSchema, MiniRenderDiv, MiniRenderDivNode;
+ beforeEach(() => {
+ miniRenderSchema = {
+ id: 'miniDiv' + id,
+ };
+
+ MiniRenderDiv = leafWrapper(components.MiniRenderDiv as any, {
+ schema: miniRenderSchema,
+ baseRenderer,
+ componentInfo: {},
+ scope: {},
+ });
+
+ MiniRenderDivNode = new Node(miniRenderSchema, {
+ componentMeta: {
+ isMinimalRenderUnit: true,
+ },
+ });
+
+ TextNode = new Node(textSchema, {
+ parent: MiniRenderDivNode,
+ });
+
+ component = renderer.create(
+ // @ts-ignore
+
+
+
+ );
})
-});
-describe('loop', () => {
- const Div = leafWrapper(components.Div as any, {
- schema: {
- id: 'div',
- },
- baseRenderer,
- componentInfo: {},
- scope: {},
+ it('make text props change', () => {
+ if (!MiniRenderDivNode.schema.props) {
+ MiniRenderDivNode.schema.props = {};
+ }
+ MiniRenderDivNode.schema.props['newPropKey'] = 'newPropValue';
+
+ makeSnapshot(component);
+
+ const inst = component.root;
+
+ const TextInst = inst.findByType(Text).children[0];
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content',
+ } as any);
+
+ expect((TextInst as any)?._fiber.stateNode.renderUnitInfo).toEqual({
+ singleRender: false,
+ minimalUnitId: 'miniDiv' + id,
+ minimalUnitName: undefined,
+ });
+
+ makeSnapshot(component);
});
- const DivNode = new Node({});
- const TextNode = new Node({});
+ it('dont render mini render component', () => {
+ const TextNode = new Node(textSchema, {
+ parent: new Node({
+ id: 'random',
+ }, {
+ componentMeta: {
+ isMinimalRenderUnit: true,
+ },
+ }),
+ });
- const Text = leafWrapper(components.Text as any, {
- schema: {
- id: 'div',
- props: {
- content: 'content'
+ renderer.create(
+ // @ts-ignore
+
+
+
+ );
+
+ const nextCount = rerenderCount + 1;
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content',
+ } as any);
+
+ expect(rerenderCount).toBe(nextCount);
+ });
+
+ it('leaf is a mock function', () => {
+ const TextNode = new Node(textSchema, {
+ parent: {
+ isEmpty: () => false,
}
- },
- baseRenderer,
- componentInfo: {},
- scope: {},
+ });
+
+ renderer.create(
+ // @ts-ignore
+
+
+
+ );
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content',
+ } as any);
});
- const component = renderer.create(
- // @ts-ignore
-
+ it('change component leaf isRoot is true', () => {
+ const TextNode = new Node(textSchema, {
+ isRoot: true,
+ });
+
+ const component = renderer.create(
-
- );
+ );
+
+ const inst = component.root;
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content',
+ } as any);
+
+ expect((inst.children[0] as any)?._fiber.stateNode.renderUnitInfo).toEqual({
+ singleRender: true,
+ });
+ });
+
+ it('change component leaf parent isRoot is true', () => {
+ const TextNode = new Node(textSchema, {
+ parent: new Node({
+ id: 'first-parent',
+ }, {
+ componentMeta: {
+ isMinimalRenderUnit: true,
+ },
+ parent: new Node({
+ id: 'rootId',
+ }, {
+ isRoot: true,
+ }),
+ })
+ });
+
+ const component = renderer.create(
+
+ );
+
+ const inst = component.root;
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content',
+ } as any);
+
+ expect((inst.children[0] as any)?._fiber.stateNode.renderUnitInfo).toEqual({
+ singleRender: false,
+ minimalUnitId: 'first-parent',
+ minimalUnitName: undefined,
+ });
+ });
+
+ it('parent is a mock leaf', () => {
+ const MiniRenderDivNode = {};
+
+ const component = renderer.create(
+ // @ts-ignore
+
+
+
+ );
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'new content to mock',
+ } as any);
+
+ makeSnapshot(component);
+ });
+
+ it('props has new children', () => {
+ MiniRenderDivNode.schema.props.children = [
+ 'children 01',
+ 'children 02',
+ ];
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: 'props'
+ });
+
+ makeSnapshot(component);
+ });
+
+ it('leaf has a loop, render from parent', () => {
+ MiniRenderDivNode = new Node(miniRenderSchema, {});
+
+ TextNode = new Node(textSchema, {
+ parent: MiniRenderDivNode,
+ hasLoop: true,
+ });
+
+ component = renderer.create(
+ // @ts-ignore
+
+
+
+ );
+
+ MiniRenderDivNode.schema.children = ['this is a new children'];
+
+ TextNode.emitPropChange({
+ key: 'content',
+ newValue: '1',
+ });
+
+ makeSnapshot(component);
+ });
+});
+
+describe('component cache', () => {
+ it('get different component with same is and different doc id', () => {
+ const baseRenderer02 = {
+ ...baseRenderer,
+ props: {
+ ...baseRenderer.props,
+ documentId: '02',
+ }
+ }
+ const Div3 = leafWrapper(components.Div as any, {
+ schema: divSchema,
+ baseRenderer: baseRenderer02,
+ componentInfo: {},
+ scope: {},
+ });
+
+ expect(Div).not.toEqual(Div3);
+ });
+
+ it('get component again and get ths cache component', () => {
+ const Div2 = leafWrapper(components.Div as any, {
+ schema: divSchema,
+ baseRenderer,
+ componentInfo: {},
+ scope: {},
+ });
+
+ expect(Div).toEqual(Div2);
+ });
+});
+
+describe('onVisibleChange', () => {
+ it('visible is false', () => {
+ TextNode.emitVisibleChange(false);
+ makeSnapshot(component);
+ });
+
+ it('visible is true', () => {
+ TextNode.emitVisibleChange(true);
+ makeSnapshot(component);
+ });
+});
+
+describe('children', () => {
+ it('this.props.children is array', () => {
+ const component = renderer.create(
+ // @ts-ignore
+
+
+
+
+ );
+
+ makeSnapshot(component);
+ });
+});
+
+describe('onChildrenChange', () => {
+ it('children is array string', () => {
+ DivNode.schema.children = [
+ 'onChildrenChange content 01',
+ 'onChildrenChange content 02'
+ ]
+ DivNode.emitChildrenChange();
+ makeSnapshot(component);
+ });
+});
+
+describe('not render leaf', () => {
+ let miniRenderSchema, MiniRenderDiv, MiniRenderDivNode;
+ beforeEach(() => {
+ miniRenderSchema = {
+ id: 'miniDiv' + id,
+ };
+
+ MiniRenderDivNode = new Node(miniRenderSchema, {
+ componentMeta: {
+ isMinimalRenderUnit: true,
+ },
+ });
+
+ nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
+
+ MiniRenderDiv = leafWrapper(components.MiniRenderDiv as any, {
+ schema: miniRenderSchema,
+ baseRenderer,
+ componentInfo: {},
+ scope: {},
+ });
+
+ TextNode = new Node(textSchema, {
+ parent: MiniRenderDivNode,
+ });
+
+ component = renderer.create(
+
+ );
+ });
+
+ it('onPropsChange', () => {
+ const nextCount = rerenderCount + 1;
+
+ MiniRenderDivNode.emitPropChange({
+ key: 'any',
+ newValue: 'any',
+ });
+
+ expect(rerenderCount).toBe(nextCount);
+ });
+
+ it('onChildrenChange', () => {
+ const nextCount = rerenderCount + 1;
+
+ MiniRenderDivNode.emitChildrenChange({
+ key: 'any',
+ newValue: 'any',
+ });
+
+ expect(rerenderCount).toBe(nextCount);
+ });
+
+ it('onVisibleChange', () => {
+ const nextCount = rerenderCount + 1;
+
+ MiniRenderDivNode.emitVisibleChange(true);
+
+ expect(rerenderCount).toBe(nextCount);
+ });
});
diff --git a/packages/renderer-core/tests/renderer/__snapshots__/renderer.test.tsx.snap b/packages/renderer-core/tests/renderer/__snapshots__/renderer.test.tsx.snap
index 2b96228ad..5ef56f775 100644
--- a/packages/renderer-core/tests/renderer/__snapshots__/renderer.test.tsx.snap
+++ b/packages/renderer-core/tests/renderer/__snapshots__/renderer.test.tsx.snap
@@ -1026,7 +1026,20 @@ exports[`JSExpression JSSlot has loop 1`] = `
forwardRef={[Function]}
useFieldIdAsDomId={false}
>
-
@@ -1044,7 +1057,20 @@ exports[`JSExpression JSSlot has loop 1`] = `
forwardRef={[Function]}
useFieldIdAsDomId={false}
>
-
@@ -1062,7 +1088,20 @@ exports[`JSExpression JSSlot has loop 1`] = `
forwardRef={[Function]}
useFieldIdAsDomId={false}
>
-
diff --git a/packages/renderer-core/tests/setup.ts b/packages/renderer-core/tests/setup.ts
index 45fe6e19b..fd6fd9e5d 100644
--- a/packages/renderer-core/tests/setup.ts
+++ b/packages/renderer-core/tests/setup.ts
@@ -11,6 +11,16 @@ jest.mock('zen-logger', () => {
};
});
+jest.mock('lodash', () => {
+ const original = jest.requireActual('lodash');
+
+ return {
+ ...original,
+ debounce: (fn) => () => fn(),
+ throttle: (fn) => () => fn(),
+ }
+})
+
export const mockConsoleWarn = jest.fn();
console.warn = mockConsoleWarn;
diff --git a/packages/renderer-core/tests/utils/components.tsx b/packages/renderer-core/tests/utils/components.tsx
index 5cb557d4b..639151612 100644
--- a/packages/renderer-core/tests/utils/components.tsx
+++ b/packages/renderer-core/tests/utils/components.tsx
@@ -1,9 +1,17 @@
import React from 'react';
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
-const Div = (props: any) => (
{props.children}
);
+const Div = ({_leaf, ...rest}: any) => (
{rest.children}
);
-const Text = (props: any) => (
{props.content}
);
+const MiniRenderDiv = ({_leaf, ...rest}: any) => {
+ return (
+
+ {rest.children}
+
+ );
+};
+
+const Text = ({_leaf, ...rest}: any) => (
{rest.content}
);
const SlotComponent = (props: any) => props.mobileSlot;
@@ -24,6 +32,7 @@ const components = {
Div,
SlotComponent,
Text,
+ MiniRenderDiv,
};
-export default components;
\ No newline at end of file
+export default components;
diff --git a/packages/renderer-core/tests/utils/node.ts b/packages/renderer-core/tests/utils/node.ts
index 2756191c6..01da07a69 100644
--- a/packages/renderer-core/tests/utils/node.ts
+++ b/packages/renderer-core/tests/utils/node.ts
@@ -6,16 +6,47 @@ export default class Node {
schema: any = {
props: {},
};
- hasLoop = false;
- constructor(schema: any) {
+ componentMeta = {};
+
+ parent;
+
+ hasLoop = () => this._hasLoop;
+
+ id;
+
+ _isRoot: false;
+
+ _hasLoop: false;
+
+ constructor(schema: any, info: any = {}) {
this.emitter = new EventEmitter();
- this.schema = schema;
+ const {
+ componentMeta,
+ parent,
+ isRoot,
+ hasLoop,
+ } = info;
+ this.schema = {
+ props: {},
+ ...schema,
+ };
+ this.componentMeta = componentMeta || {};
+ this.parent = parent;
+ this.id = schema.id;
+ this._isRoot = isRoot;
+ this._hasLoop = hasLoop;
}
- mockLoop() {
- this.hasLoop = true;
- }
+ isRoot = () => this._isRoot;
+
+ // componentMeta() {
+ // return this.componentMeta;
+ // }
+
+ // mockLoop() {
+ // // this.hasLoop = true;
+ // }
onChildrenChange(fn: any) {
this.emitter.on('onChildrenChange', fn);
@@ -24,6 +55,10 @@ export default class Node {
}
}
+ emitChildrenChange() {
+ this.emitter?.emit('onChildrenChange', {});
+ }
+
onPropChange(fn: any) {
this.emitter.on('onPropChange', fn);
return () => {
@@ -31,11 +66,14 @@ export default class Node {
}
}
- emitPropChange(val: PropChangeOptions) {
- this.schema.props = {
- ...this.schema.props,
- [val.key + '']: val.newValue,
+ emitPropChange(val: PropChangeOptions, skip?: boolean) {
+ if (!skip) {
+ this.schema.props = {
+ ...this.schema.props,
+ [val.key + '']: val.newValue,
+ }
}
+
this.emitter?.emit('onPropChange', val);
}