Files
Hippy/renderer/voltron/test/render_node_test.dart
churchill-zhang fce92a2dc8 fix(dom): implement the MoveRenderNode interface (#3070)
* feat(dom): add MoveRenderNode

* feat(dom): Implement the MoveRenderNode interface

* feat(dom): adjust layer optimization algorithm

* feat(ios): support moving nodes for ios

* feat(ios): support moving nodes for ios

* feat(ios): support moving views for ios

* feat(android): support move child node

* feat(android): support move child node

* fix(android): virtual node move should mark dirty

* feat(voltron): support moving views for voltron

* fix(android): add update node when move

* feat(voltron): modify private code syntax

* feat(voltron): update voltron recombine test

---------

Co-authored-by: luomy <ozonelmy@163.com>
Co-authored-by: maxli <maxli@tencent.com>
Co-authored-by: henryjin0511 <443836568@qq.com>
Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com>
2023-03-24 07:07:54 +00:00

382 lines
12 KiB
Dart

//
// Tencent is pleased to support the open source community by making
// Hippy available.
//
// Copyright (C) 2022 THL A29 Limited, a Tencent company.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// @dart=2.9
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:voltron_renderer/render.dart';
import 'package:voltron_renderer/widget.dart';
import './util/render_context.dart';
import 'util/render_op_util.dart';
void main() {
RenderContext renderContext;
RenderOperatorRunner renderOpRunner;
RootWidgetViewModel rootWidgetViewModel;
RenderOpUtil renderOpUtil;
setUp(() {
WidgetsFlutterBinding.ensureInitialized();
renderContext = getRenderContext();
renderOpRunner = RenderOperatorRunner();
renderOpRunner.bindRenderContext(renderContext);
rootWidgetViewModel = RootWidgetViewModel(1);
renderContext.rootViewModelMap[rootWidgetViewModel.id] = rootWidgetViewModel;
renderOpUtil = RenderOpUtil(
rootWidgetViewModel: rootWidgetViewModel,
renderOpRunner: renderOpRunner,
renderContext: renderContext,
);
renderOpUtil.init();
});
tearDown(() {});
group('RenderNode:', () {
group('addNode operator', () {
test('should create a renderNode', () {
var ops = [
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "item", "id": 0}
},
"styles": {"backgroundColor": 4292129211.0, "height": 30.0, "width": 20.0}
})
];
renderOpUtil.runRenderOp(ops);
var node = rootWidgetViewModel.renderTree.getRenderNode(1);
expect(node.id, 1);
expect(node.style.height, 30.0);
expect(node.style.width, 20.0);
});
test('should create a renderTree', () {
var ops = [
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 0,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "node1", "id": 0}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 2, props: {
"index": 0,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node2", "id": 1}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 3, props: {
"index": 1,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node3", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 4, props: {
"index": 1,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node4", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 5, props: {
"index": 0,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node5", "id": 2}
},
}),
];
renderOpUtil.runRenderOp(ops);
var rootNode = rootWidgetViewModel.renderTree.rootNode;
expect(rootNode.children, hasLength(1), reason: 'rootNode has only one child');
var node = rootNode.children[0];
expect(node.id, 1, reason: 'child of rootNode is node1');
expect(node.children, hasLength(2), reason: 'node1 should have two children');
expect(node.children[0].id, 2, reason: 'first child of node1 should be node2');
expect(node.children[1], isA<RenderNode>());
expect(node.children[1].id, 3, reason: 'second child of node1 should be node3');
var node3 = node.children[1];
expect(node3.childCount, 2, reason: 'node3 should have two children');
expect(node3.children[0].id, 5, reason: 'first child of node3 shoule be node5');
expect(node3.children[1].id, 4, reason: 'second child of node3 shoule be node4');
});
test('should add node to exiting tree', () {
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "item", "id": 0}
},
"styles": {"backgroundColor": 4292129211.0, "height": 30.0, "width": 20.0}
})
]);
var node = rootWidgetViewModel.renderTree.getRenderNode(1);
expect(node.id, 1);
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.addNode, nodeId: 2, props: {
"index": 0,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node2", "id": 0}
}
})
]);
expect(node.childCount, 1, reason: 'node1 should has one child');
expect(node.children[0].id, 2, reason: 'node2 is child of node1');
});
});
group('deleteNode operator', () {
test('should delete render node', () {
var opsForbuildRenderTree = [
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 0,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "node1", "id": 0}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 2, props: {
"index": 0,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node2", "id": 1}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 3, props: {
"index": 1,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node3", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 4, props: {
"index": 1,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node4", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 5, props: {
"index": 0,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node5", "id": 2}
},
}),
];
renderOpUtil.runRenderOp(opsForbuildRenderTree);
var node1 = rootWidgetViewModel.renderTree.getRenderNode(1);
var node3 = rootWidgetViewModel.renderTree.getRenderNode(3);
expect(node3.childCount, 2, reason: 'node3 should have 2 children');
var node5 = node3.children[0];
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.deleteNode, nodeId: 5),
]);
expect(node3.childCount, 1, reason: 'node3 should have 1 child after deleted');
expect(node5.isDelete, isTrue);
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.deleteNode, nodeId: 3),
]);
expect(node3.isDelete, isTrue, reason: 'node3 is deleted');
expect(node1.childCount, 1, reason: 'node1 should has only one child');
});
});
test('recombineNode operator', () {
var opsForbuildRenderTree = [
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 0,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "node1", "id": 0}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 2, props: {
"index": 0,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node2", "id": 1}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 3, props: {
"index": 1,
"name": "View",
"pid": 1,
"props": {
"attributes": {"class": "node3", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 4, props: {
"index": 1,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node4", "id": 2}
},
}),
RenderOp(type: RenderOpType.addNode, nodeId: 5, props: {
"index": 0,
"name": "View",
"pid": 3,
"props": {
"attributes": {"class": "node5", "id": 2}
},
}),
];
renderOpUtil.runRenderOp(opsForbuildRenderTree);
var tree = rootWidgetViewModel.renderTree;
var node1 = rootWidgetViewModel.renderTree.getRenderNode(1);
var node2 = rootWidgetViewModel.renderTree.getRenderNode(2);
var node3 = rootWidgetViewModel.renderTree.getRenderNode(3);
var node4 = rootWidgetViewModel.renderTree.getRenderNode(4);
var node5 = rootWidgetViewModel.renderTree.getRenderNode(5);
expect(node5.parent.id, 3, reason: 'parent of node5 is node3 before move');
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.recombineNode, nodeId: 2, props: {
"move_id": [4, 5],
"move_pid": 3,
"move_index": 0,
}),
]);
expect(node5.parent.id, 2, reason: 'parent of node5 is node2 after move');
expect(node2.childCount, 2, reason: 'node2 should have 2 children');
expect(node3.childCount, isZero, reason: 'node3 should has no child now');
});
group('updateNode operator', () {
setUp(() {
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "item", "id": 0}
},
"styles": {"backgroundColor": 4292129211.0, "height": 30.0, "width": 20.0}
})
]);
});
test('should update props', () {
var node = rootWidgetViewModel.renderTree.getRenderNode(1);
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.updateNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "item1", "id": 0},
"backgroundColor": 4292129211.0,
"height": 32.0,
"width": 24.0
},
})
]);
expect(node.styleWidth, 24.0);
expect(node.styleHeight, 32.0);
// renderOpUtil.printRenderNode(1);
});
test('should update styles', () {
var node = rootWidgetViewModel.renderTree.getRenderNode(1);
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.updateNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"backgroundColor": 0xFFABC123,
},
})
]);
expect(node.viewModel.backgroundColor, equals(const Color(0xFFABC123)));
});
});
test('updateLayout operator should update layout properties', () {
var ops = [
RenderOp(type: RenderOpType.addNode, nodeId: 1, props: {
"index": 1,
"name": "View",
"pid": 0,
"props": {
"attributes": {"class": "item", "id": 0}
},
"styles": {"backgroundColor": 4292129211.0, "height": 30.0, "width": 20.0}
})
];
renderOpUtil.runRenderOp(ops);
var node = rootWidgetViewModel.renderTree.getRenderNode(1);
renderOpUtil.runRenderOp([
RenderOp(type: RenderOpType.updateLayout, nodeId: 1, props: {
"layout_nodes": [
[1, 12.0, 13.0, 14.0, 15.0], // id,left,top,width,height
],
})
]);
expect(node.layoutX, 12.0);
expect(node.layoutY, 13.0);
expect(node.layoutWidth, 14.0);
expect(node.layoutHeight, 15.0);
});
});
}