{},
{
get(_, key: any) {
- return handles[key]()
+ if (handles.hasOwnProperty(key)) {
+ return handles[key]()
+ }
}
// TODO should be readonly
}
nodeOps,
NodeTypes,
TestElement,
- TestText
+ TestText,
+ dumpOps,
+ NodeOpTypes,
+ nextTick,
+ observable,
+ resetOps
} from '../src'
describe('test renderer', () => {
it('should work', () => {
class App extends Component {
+ data() {
+ return {
+ id: 'test',
+ text: 'hello'
+ }
+ }
render() {
return h(
'div',
{
- id: 'test'
+ id: this.id
},
- 'hello'
+ this.text
)
}
}
expect(text.text).toBe('hello')
})
- it('should record ops', () => {
- // TODO
+ it('should record ops', async () => {
+ const state = observable({
+ id: 'test',
+ text: 'hello'
+ })
+
+ class App extends Component {
+ render() {
+ return h(
+ 'div',
+ {
+ id: state.id
+ },
+ state.text
+ )
+ }
+ }
+ const root = nodeOps.createElement('div')
+
+ resetOps()
+ render(h(App), root)
+ const ops = dumpOps()
+
+ expect(ops.length).toBe(5)
+
+ expect(ops[0]).toEqual({
+ type: NodeOpTypes.CREATE,
+ nodeType: NodeTypes.ELEMENT,
+ tag: 'div',
+ targetNode: root.children[0]
+ })
+
+ expect(ops[1]).toEqual({
+ type: NodeOpTypes.PATCH,
+ targetNode: root.children[0],
+ propKey: 'id',
+ propPrevValue: null,
+ propNextValue: 'test'
+ })
+
+ expect(ops[2]).toEqual({
+ type: NodeOpTypes.CREATE,
+ nodeType: NodeTypes.TEXT,
+ text: 'hello',
+ targetNode: (root.children[0] as TestElement).children[0]
+ })
+
+ expect(ops[3]).toEqual({
+ type: NodeOpTypes.APPEND,
+ targetNode: (root.children[0] as TestElement).children[0],
+ parentNode: root.children[0]
+ })
+
+ expect(ops[4]).toEqual({
+ type: NodeOpTypes.APPEND,
+ targetNode: root.children[0],
+ parentNode: root
+ })
+
+ // test update ops
+ state.id = 'foo'
+ state.text = 'bar'
+ await nextTick()
+
+ const updateOps = dumpOps()
+ expect(updateOps.length).toBe(2)
+
+ expect(updateOps[0]).toEqual({
+ type: NodeOpTypes.PATCH,
+ targetNode: root.children[0],
+ propKey: 'id',
+ propPrevValue: 'test',
+ propNextValue: 'foo'
+ })
+
+ expect(updateOps[1]).toEqual({
+ type: NodeOpTypes.SET_TEXT,
+ targetNode: (root.children[0] as TestElement).children[0],
+ text: 'bar'
+ })
})
})
import { createRenderer, VNode } from '@vue/core'
import { nodeOps, TestElement } from './nodeOps'
-
-function patchData(
- el: TestElement,
- key: string,
- prevValue: any,
- nextValue: any
-) {
- el.props[key] = nextValue
-}
+import { patchData } from './patchData'
const { render: _render } = createRenderer({
nodeOps,
export type TestNode = TestElement | TestText
-const enum OpTypes {
+export const enum NodeOpTypes {
CREATE = 'create',
INSERT = 'insert',
APPEND = 'append',
REMOVE = 'remove',
SET_TEXT = 'setText',
CLEAR = 'clearContent',
- NEXT_SIBLING = 'nextSibling',
- PARENT_NODE = 'parentNode'
+ PATCH = 'patch'
}
-interface Op {
- type: OpTypes
+export interface NodeOp {
+ type: NodeOpTypes
nodeType?: NodeTypes
tag?: string
text?: string
targetNode?: TestNode
parentNode?: TestElement
refNode?: TestNode
+ propKey?: string
+ propPrevValue?: any
+ propNextValue?: any
}
let nodeId: number = 0
-let isRecording: boolean = false
-let recordedOps: Op[] = []
+let recordedNodeOps: NodeOp[] = []
-function logOp(op: Op) {
- if (isRecording) {
- recordedOps.push(op)
- }
+export function logNodeOp(op: NodeOp) {
+ recordedNodeOps.push(op)
}
-export function startRecordingOps() {
- if (!isRecording) {
- isRecording = true
- recordedOps = []
- } else {
- throw new Error(
- '`startRecordingOps` called when there is already an active session.'
- )
- }
+export function resetOps() {
+ recordedNodeOps = []
}
-export function dumpOps(): Op[] {
- if (!isRecording) {
- throw new Error(
- '`dumpOps` called without a recording session. ' +
- 'Call `startRecordingOps` first to start a session.'
- )
- }
- isRecording = false
- return recordedOps.slice()
+export function dumpOps(): NodeOp[] {
+ const ops = recordedNodeOps.slice()
+ resetOps()
+ return ops
}
function createElement(tag: string): TestElement {
props: {},
parentNode: null
}
- logOp({
- type: OpTypes.CREATE,
+ logNodeOp({
+ type: NodeOpTypes.CREATE,
nodeType: NodeTypes.ELEMENT,
targetNode: node,
tag
text,
parentNode: null
}
- logOp({
- type: OpTypes.CREATE,
+ logNodeOp({
+ type: NodeOpTypes.CREATE,
nodeType: NodeTypes.TEXT,
targetNode: node,
text
}
function setText(node: TestText, text: string) {
- logOp({
- type: OpTypes.SET_TEXT,
+ logNodeOp({
+ type: NodeOpTypes.SET_TEXT,
targetNode: node,
text
})
}
function appendChild(parent: TestElement, child: TestNode) {
- logOp({
- type: OpTypes.APPEND,
+ logNodeOp({
+ type: NodeOpTypes.APPEND,
targetNode: child,
parentNode: parent
})
console.error('parent: ', parent)
throw new Error('ref is not a child of parent')
}
- logOp({
- type: OpTypes.INSERT,
+ logNodeOp({
+ type: NodeOpTypes.INSERT,
targetNode: child,
parentNode: parent,
refNode: ref
}
function removeChild(parent: TestElement, child: TestNode) {
- logOp({
- type: OpTypes.REMOVE,
+ logNodeOp({
+ type: NodeOpTypes.REMOVE,
targetNode: child,
parentNode: parent
})
}
function clearContent(node: TestNode) {
- logOp({
- type: OpTypes.CLEAR,
+ logNodeOp({
+ type: NodeOpTypes.CLEAR,
targetNode: node
})
if (node.type === NodeTypes.ELEMENT) {
}
function parentNode(node: TestNode): TestElement | null {
- logOp({
- type: OpTypes.PARENT_NODE,
- targetNode: node
- })
return node.parentNode
}
function nextSibling(node: TestNode): TestNode | null {
- logOp({
- type: OpTypes.NEXT_SIBLING,
- targetNode: node
- })
const parent = node.parentNode
if (!parent) {
return null
nextSibling,
querySelector
}
+
+export function patchData() {}
--- /dev/null
+import { TestElement, logNodeOp, NodeOpTypes } from './nodeOps'
+
+export function patchData(
+ el: TestElement,
+ key: string,
+ prevValue: any,
+ nextValue: any
+) {
+ logNodeOp({
+ type: NodeOpTypes.PATCH,
+ targetNode: el,
+ propKey: key,
+ propPrevValue: prevValue,
+ propNextValue: nextValue
+ })
+ el.props[key] = nextValue
+}