Skip to content

Commit 0a972c3

Browse files
clean up and testing progress on create tree
1 parent d1c7df8 commit 0a972c3

File tree

5 files changed

+237
-197
lines changed

5 files changed

+237
-197
lines changed

demo-app/src/client/Components/Increment.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { BoardText } from '../../types';
44
import { Function } from 'lodash';
55
function Increment() {
66
const [count, setCount] = useState(0);
7-
const [value, setValue]: [BoardText, any] = useState('-');
87
return (
98
<div>
109
<button className='increment' onClick={() => setCount(count + 1)}>

src/backend/__tests__/masterTree.test.ts

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,112 @@
11
import createComponentActionsRecord from '../controllers/createTree/createComponentActionsRecord';
22
import { Fiber } from '../types/backendTypes';
33
import componentActionsRecord from '../models/masterState';
4+
import createTree from '../controllers/createTree/createTree';
5+
import Tree from '../models/tree';
6+
import {
7+
allowedComponentTypes,
8+
nextJSDefaultComponent,
9+
remixDefaultComponents,
10+
} from '../models/filterConditions';
411

512
describe('master tree tests', () => {
6-
describe('createComponentActionsRecord', () => {
7-
let mockFiberNode: Fiber;
8-
beforeEach(() => {
9-
// create a mock Fiber node with relevant properties
10-
mockFiberNode = {
11-
sibling: null,
12-
stateNode: {},
13-
child: null,
14-
memoizedState: null,
15-
elementType: {},
16-
tag: 2, // IndeterminateComponent
17-
key: null,
18-
type: null,
19-
index: 0,
20-
memoizedProps: null,
21-
dependencies: null,
22-
_debugHookTypes: [],
23-
};
24-
// clear the saved component actions record
25-
componentActionsRecord.clear();
13+
let mockTree = new Tree('root', 'root');
14+
let mockFiberNode: Fiber;
15+
let mockSiblingNode: Fiber;
16+
let mockChildNode: Fiber;
17+
beforeEach(() => {
18+
// create a mock Fiber node with relevant properties
19+
mockFiberNode = {
20+
sibling: null,
21+
stateNode: {},
22+
child: null,
23+
memoizedState: null,
24+
elementType: {},
25+
tag: 2, // IndeterminateComponent
26+
key: null,
27+
type: null,
28+
index: 0,
29+
memoizedProps: null,
30+
dependencies: null,
31+
_debugHookTypes: [],
32+
};
33+
34+
mockChildNode = {
35+
...mockFiberNode,
36+
tag: 1,
37+
elementType: { name: 'child' },
38+
stateNode: { state: { counter: 0 }, props: { start: 0 } },
39+
};
40+
mockSiblingNode = {
41+
...mockFiberNode,
42+
tag: 0,
43+
elementType: { name: 'sibling' },
44+
memoizedState: { memoizedState: 1, queue: [{}, { state: { value: 'test' } }], next: null },
45+
};
46+
// clear the saved component actions record
47+
componentActionsRecord.clear();
48+
});
49+
describe('create tree tests', () => {
50+
it('should return a Tree if we pass in a empty fiber node', () => {
51+
const tree = createTree(mockFiberNode);
52+
const children = tree.children;
53+
54+
expect(tree).toBeInstanceOf(Tree);
55+
expect(tree.name).toEqual('root');
56+
expect(tree.state).toEqual('root');
57+
expect(children[0].name).toEqual('nameless');
58+
expect(children[0].state).toEqual('stateless');
59+
});
60+
61+
it('should filter out NextJS default components with no children or siblings', () => {
62+
for (let name of nextJSDefaultComponent) {
63+
mockFiberNode.elementType.name = name;
64+
const tree = createTree(mockFiberNode);
65+
const newTree = new Tree('root', 'root');
66+
expect(tree).toEqual(newTree);
67+
}
68+
});
69+
70+
it('should filter out NextJS default components with children and/or siblings', () => {
71+
for (let name of nextJSDefaultComponent) {
72+
mockFiberNode.elementType.name = name;
73+
mockFiberNode.child = mockChildNode;
74+
mockFiberNode.sibling = mockSiblingNode;
75+
const tree = createTree(mockFiberNode);
76+
const children = tree.children;
77+
const firstChild = children[0];
78+
const secondChild = children[1];
79+
expect(children.length).toEqual(2);
80+
// expect(firstChild.componentData?.state).toEqual(2);
81+
}
82+
});
83+
84+
it('should filter out remix default components with no children or siblings', () => {
85+
for (let name of remixDefaultComponents) {
86+
mockFiberNode.elementType.name = name;
87+
const tree = createTree(mockFiberNode);
88+
}
2689
});
2790

91+
it('should only traverse allowed components', () => {
92+
for (let tag of allowedComponentTypes) {
93+
mockFiberNode.elementType.tag = tag;
94+
const tree = createTree(mockFiberNode);
95+
const children = tree.children;
96+
97+
expect(tree.name).toEqual('root');
98+
expect(tree.state).toEqual('root');
99+
expect(children[0].name).toEqual('nameless');
100+
expect(children[0].state).toEqual('stateless');
101+
}
102+
});
103+
});
104+
105+
describe('add sibling', () => {});
106+
107+
describe('add children', () => {});
108+
109+
describe('createComponentActionsRecord', () => {
28110
it('should save a new component action record if the Fiber node is a stateful class component', () => {
29111
mockFiberNode.tag = 1; // ClassComponent
30112
mockFiberNode.stateNode = {
@@ -82,24 +164,7 @@ describe('master tree tests', () => {
82164
});
83165

84166
it('should return the correct hooks array for a given component index', () => {
85-
const component1 = { state: 'dummy state', props: {} };
86-
const component2 = { state: 'dummy state2', props: {} };
87-
const component3 = { state: 'dummy state3', props: {} };
88-
componentActionsRecord.saveNew(component1);
89-
componentActionsRecord.saveNew(component2);
90-
componentActionsRecord.saveNew(component3);
91167
// create a mock component action record
92-
const mockComponentActionRecord = {
93-
index: 0,
94-
hooks: ['mock hook 1', 'mock hook 2', 'mock hook 3'],
95-
};
96-
componentActionsRecord.addOrUpdateComponent(mockComponentActionRecord);
97-
98-
// call the getComponentByIndexHooks function with the mock index
99-
const hooksArray = componentActionsRecord.getComponentByIndexHooks(0);
100-
101-
// assert that the returned hooks array matches the expected value
102-
expect(hooksArray).toEqual(mockComponentActionRecord.hooks);
103168
});
104169

105170
it('should not save a new component action record if the Fiber node is not a relevant component type', () => {

src/backend/controllers/createTree/createComponentActionsRecord.ts

Lines changed: 93 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -24,129 +24,111 @@ import {
2424
* 1. Traverse from FiberRootNode
2525
* 2. If the component is stateful, extract its update methods & push to the `componentActionRecord` array
2626
* @param currentFiberNode A Fiber object
27-
* @param circularComponentTable A table content visited Fiber nodes
2827
*/
2928
export default function createComponentActionsRecord(currentFiberNode: Fiber): void {
30-
const circularComponentTable: Set<Fiber> = new Set();
31-
_createComponentActionsRecord(currentFiberNode);
29+
// ------------------OBTAIN DATA FROM THE CURRENT FIBER NODE----------------
30+
// Destructure the current fiber node:
31+
const {
32+
sibling,
33+
stateNode,
34+
child,
35+
// with memoizedState we can grab the root type and construct an Abstract Syntax Tree from the hooks structure using Acorn in order to extract the hook getters and match them with their corresponding setters in an object
36+
memoizedState,
37+
elementType,
38+
tag,
39+
} = currentFiberNode;
3240

33-
/**
34-
* This is a helper function to recursively traverse the React Fiber Tree and craft the snapshot tree to send to front end
35-
* @param currentFiberNode A Fiber object
36-
*/
37-
function _createComponentActionsRecord(currentFiberNode: Fiber): void {
38-
// ----------------------UPDATE VISITED FIBER NODE SET----------------------
39-
// Base Case: if has visited the component, return
40-
if (circularComponentTable.has(currentFiberNode)) {
41-
return;
42-
} else {
43-
circularComponentTable.add(currentFiberNode);
44-
}
45-
46-
// ------------------OBTAIN DATA FROM THE CURRENT FIBER NODE----------------
47-
// Destructure the current fiber node:
48-
const {
49-
sibling,
50-
stateNode,
51-
child,
52-
// with memoizedState we can grab the root type and construct an Abstract Syntax Tree from the hooks structure using Acorn in order to extract the hook getters and match them with their corresponding setters in an object
53-
memoizedState,
54-
elementType,
55-
tag,
56-
} = currentFiberNode;
57-
58-
// Obtain component name:
59-
const componentName =
60-
elementType?._context?.displayName || //For ContextProvider
61-
elementType?._result?.name || //For lazy Component
62-
elementType?.render?.name ||
63-
elementType?.name ||
64-
'nameless';
41+
// Obtain component name:
42+
const componentName =
43+
elementType?._context?.displayName || //For ContextProvider
44+
elementType?._result?.name || //For lazy Component
45+
elementType?.render?.name ||
46+
elementType?.name ||
47+
'nameless';
6548

66-
// console.log('createComponentActionsRecord', {
67-
// currentFiberNode,
68-
// // tag,
69-
// // elementType,
70-
// componentName:
71-
// elementType?._context?.displayName || //For ContextProvider
72-
// elementType?._result?.name || //For lazy Component
73-
// elementType?.render?.name ||
74-
// elementType?.name ||
75-
// elementType,
76-
// // memoizedState,
77-
// // stateNode,
78-
// // _debugHookTypes,
79-
// });
49+
// console.log('createComponentActionsRecord', {
50+
// currentFiberNode,
51+
// // tag,
52+
// // elementType,
53+
// componentName:
54+
// elementType?._context?.displayName || //For ContextProvider
55+
// elementType?._result?.name || //For lazy Component
56+
// elementType?.render?.name ||
57+
// elementType?.name ||
58+
// elementType,
59+
// // memoizedState,
60+
// // stateNode,
61+
// // _debugHookTypes,
62+
// });
8063

81-
// --------------------FILTER COMPONENTS/FIBER NODE-------------------------
82-
/**
83-
* For the snapshot tree,
84-
* 1. We will only interested in components that are one of these types: Function Component, Class Component, Indeterminate Component or Context Provider.
85-
* NOTE: this list of components may change depending on future use
86-
* 2. If user use Next JS, filter out default NextJS components
87-
* 3. If user use Remix JS, filter out default Remix components
88-
*/
64+
// --------------------FILTER COMPONENTS/FIBER NODE-------------------------
65+
/**
66+
* For the snapshot tree,
67+
* 1. We will only interested in components that are one of these types: Function Component, Class Component, Indeterminate Component or Context Provider.
68+
* NOTE: this list of components may change depending on future use
69+
* 2. If user use Next JS, filter out default NextJS components
70+
* 3. If user use Remix JS, filter out default Remix components
71+
*/
8972

90-
if (
91-
!allowedComponentTypes.has(tag) ||
92-
nextJSDefaultComponent.has(componentName) ||
93-
remixDefaultComponents.has(componentName)
94-
) {
95-
// -------------------TRAVERSE TO NEXT FIBERNODE------------------------
96-
// If currentFiberNode has children, recurse on children
97-
if (child) _createComponentActionsRecord(child);
73+
if (
74+
!allowedComponentTypes.has(tag) ||
75+
nextJSDefaultComponent.has(componentName) ||
76+
remixDefaultComponents.has(componentName)
77+
) {
78+
// -------------------TRAVERSE TO NEXT FIBERNODE------------------------
79+
// If currentFiberNode has children, recurse on children
80+
if (child) createComponentActionsRecord(child);
9881

99-
// If currentFiberNode has siblings, recurse on siblings
100-
if (sibling) {
101-
_createComponentActionsRecord(sibling);
102-
}
103-
// ---------RETURN THE TREE OUTPUT & PASS TO FRONTEND FOR RENDERING-------
104-
return;
82+
// If currentFiberNode has siblings, recurse on siblings
83+
if (sibling) {
84+
createComponentActionsRecord(sibling);
10585
}
86+
// ---------RETURN THE TREE OUTPUT & PASS TO FRONTEND FOR RENDERING-------
87+
return;
88+
}
10689

107-
// ---------OBTAIN STATE & SET STATE METHODS FROM CLASS COMPONENT-----------
108-
// Check if node is a stateful class component when user use setState.
109-
// If user use setState to define/manage state, the state object will be stored in stateNode.state => grab the state object stored in the stateNode.state
110-
// Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
111-
if ((tag === ClassComponent || tag === IndeterminateComponent) && stateNode?.state) {
112-
// Save component setState() method to our componentActionsRecord for use during timeJump
113-
componentActionsRecord.saveNew(stateNode);
114-
}
90+
// ---------OBTAIN STATE & SET STATE METHODS FROM CLASS COMPONENT-----------
91+
// Check if node is a stateful class component when user use setState.
92+
// If user use setState to define/manage state, the state object will be stored in stateNode.state => grab the state object stored in the stateNode.state
93+
// Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
94+
if ((tag === ClassComponent || tag === IndeterminateComponent) && stateNode?.state) {
95+
// Save component setState() method to our componentActionsRecord for use during timeJump
96+
componentActionsRecord.saveNew(stateNode);
97+
}
11598

116-
// --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
117-
// Check if node is a stateful class component when user use setState.
118-
// If user use useState to define/manage state, the state object will be stored in memoizedState.queue => grab the state object stored in the memoizedState.queue
119-
if (
120-
(tag === FunctionComponent ||
121-
tag === IndeterminateComponent ||
122-
//TODO: Need to figure out why we need context provider
123-
tag === ContextProvider) &&
124-
memoizedState
125-
) {
126-
if (memoizedState.queue) {
127-
try {
128-
// Hooks states are stored as a linked list using memoizedState.next,
129-
// so we must traverse through the list and get the states.
130-
// We then store them along with the corresponding memoizedState.queue,
131-
// which includes the dispatch() function we use to change their state.
132-
const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
133-
hooksStates.forEach(({ component }) => {
134-
// Save component's state and dispatch() function to our record for future time-travel state changing. Add record index to snapshot so we can retrieve.
135-
componentActionsRecord.saveNew(component);
136-
});
137-
} catch (err) {
138-
console.log('ERROR: Failed Element during JSX parsing', {
139-
componentName,
140-
});
141-
}
99+
// --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
100+
// Check if node is a stateful class component when user use setState.
101+
// If user use useState to define/manage state, the state object will be stored in memoizedState.queue => grab the state object stored in the memoizedState.queue
102+
if (
103+
(tag === FunctionComponent ||
104+
tag === IndeterminateComponent ||
105+
//TODO: Need to figure out why we need context provider
106+
tag === ContextProvider) &&
107+
memoizedState
108+
) {
109+
if (memoizedState.queue) {
110+
try {
111+
// Hooks states are stored as a linked list using memoizedState.next,
112+
// so we must traverse through the list and get the states.
113+
// We then store them along with the corresponding memoizedState.queue,
114+
// which includes the dispatch() function we use to change their state.
115+
const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
116+
hooksStates.forEach(({ component }) => {
117+
// Save component's state and dispatch() function to our record for future time-travel state changing. Add record index to snapshot so we can retrieve.
118+
componentActionsRecord.saveNew(component);
119+
});
120+
} catch (err) {
121+
console.log('ERROR: Failed Element during JSX parsing', {
122+
componentName,
123+
});
142124
}
143125
}
126+
}
144127

145-
// ---------------------TRAVERSE TO NEXT FIBERNODE--------------------------
146-
// If currentFiberNode has children, recurse on children
147-
if (child) _createComponentActionsRecord(child);
128+
// ---------------------TRAVERSE TO NEXT FIBERNODE--------------------------
129+
// If currentFiberNode has children, recurse on children
130+
if (child) createComponentActionsRecord(child);
148131

149-
// If currentFiberNode has siblings, recurse on siblings
150-
if (sibling) _createComponentActionsRecord(sibling);
151-
}
132+
// If currentFiberNode has siblings, recurse on siblings
133+
if (sibling) createComponentActionsRecord(sibling);
152134
}

0 commit comments

Comments
 (0)