Skip to content
This repository has been archived by the owner on Apr 25, 2018. It is now read-only.

Commit

Permalink
Unit test ElementProxy and make related fixes
Browse files Browse the repository at this point in the history
In addition to the tests, other changes are:
* Updated `ElementProxy.querySelector` to return `null`
when no matching element is found
* Fixed `ElementProxy#children()` to be `ElementProxy#childNodes`
to support text nodes as children
* Added `_emitMount` and `_emitUnmount` methods to be stubbed by unit test
  • Loading branch information
brandonpayton committed May 10, 2016
1 parent aa78f11 commit 60c89c3
Show file tree
Hide file tree
Showing 2 changed files with 229 additions and 9 deletions.
26 changes: 17 additions & 9 deletions src/ElementProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,31 @@ import {NodeProxy} from './types'
export class ElementProxy {
_node: HTMLElement;

// Expose these so they can be stubbed for unit test
_emitMount: Function;
_emitUnmount: Function;

constructor (node: HTMLElement) {
this._node = node
}

emitMount (fn: Function): void {
emitMount(this._node, fn)
this._emitMount(this._node, fn)
}

emitUnmount (fn: Function): void {
emitUnmount(this._node, fn)
// Rely on prototype method so it can be stubbed for unit test
this._emitUnmount(this._node, fn)
}

children (): HTMLCollection {
return this._node.children
childNodes (): NodeList {
return this._node.childNodes
}

replaceChild (childProxy: NodeProxy, index: number): void {
const node = this._node
const child = childProxy._node
const replaced = node.children[index]
const replaced = node.childNodes[index]

if (isDefined(replaced)) {
node.replaceChild(child, replaced)
Expand All @@ -43,7 +48,7 @@ export class ElementProxy {
insertChild (childProxy: NodeProxy, index: number): void {
const node = this._node
const child = childProxy._node
const before: Node = node.children[index]
const before: Node = node.childNodes[index]

if (isDefined(before)) {
node.insertBefore(child, before)
Expand Down Expand Up @@ -136,12 +141,15 @@ export class ElementProxy {
return new ElementProxy(node)
}

static querySelector (selector: string): ElementProxy {
const node: HTMLElement = document.querySelector(selector)
return new ElementProxy(node)
static querySelector (selector: string): ?ElementProxy {
const node: ?HTMLElement = document.querySelector(selector)
return node ? new ElementProxy(node) : null
}

static fromElement (node: HTMLElement): ElementProxy {
return new ElementProxy(node)
}
}

set(ElementProxy.prototype, `_emitMount`, emitMount)
set(ElementProxy.prototype, `_emitUnmount`, emitUnmount)
212 changes: 212 additions & 0 deletions src/__test__/ElementProxy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/* @flow */

import document from 'global/document'
import {ElementProxy} from '../ElementProxy'
import {TextProxy} from '../TextProxy'

describe(`ElementProxy`, () => {
it(`creates an element node`, () => {
const createdTagName = `div`
const elementProxy = ElementProxy.createElement(createdTagName)

const node = elementProxy._node
assert.equal(node.nodeType, Node.ELEMENT_NODE)

const expectedTagName = createdTagName.toUpperCase()
assert.equal(node.tagName, expectedTagName)
})

it(`provides proxied querySelector access to its document`, () => {
const elementNode = document.createElement(`div`)
elementNode.className = `testClass`
document.body.appendChild(elementNode)

try {
const elementProxy = ElementProxy.querySelector(`.testClass`)
assert.ok(elementProxy instanceof ElementProxy)
assert.equal(elementProxy && elementProxy._node, elementNode)

const nonexistentElementProxy = ElementProxy.querySelector(`.nonexistentClass`)
assert.equal(nonexistentElementProxy, null)
} finally {
elementNode.parentNode.removeChild(elementNode)
}
})

it(`provides facility to emit mount and unmount events`, () => {
const elementProxy = ElementProxy.createElement(`div`)

assert.ok(`emitMount` in elementProxy)
assert.ok(`emitUnmount` in elementProxy)

// Simply verify that _emitMount and _emitUnmount are called as expected,
// assuming the implementations are tested elsewhere.
const expectedNode = elementProxy._node
const expectedMountCallback = () => {}
const expectedUnmountCallback = () => {}

let actualMountNode = null
let actualMountCallback = null
elementProxy._emitMount = (node, callback) => {
actualMountNode = node
actualMountCallback = callback
}
elementProxy.emitMount(expectedMountCallback)
assert.equal(actualMountNode, expectedNode)
assert.equal(actualMountCallback, expectedMountCallback)

let actualUnmountNode = null
let actualUnmountCallback = null
elementProxy._emitUnmount = (node, callback) => {
actualUnmountNode = node
actualUnmountCallback = callback
}
elementProxy.emitUnmount(expectedUnmountCallback)
assert.equal(actualUnmountNode, expectedNode)
assert.equal(actualUnmountCallback, expectedUnmountCallback)
})

const createTestElement = () => ElementProxy.createElement(`div`)
const createTestTextNode = () => TextProxy.createTextNode(`some-text`)

it(`appends a child`, () => {
const testAppend = (createTestProxy, createTestProxy2) => {
const parentProxy = ElementProxy.createElement(`div`)
const childNodes = parentProxy.childNodes()

assert.equal(childNodes.length, 0)

const childProxy = createTestProxy()
parentProxy.insertChild(childProxy, childNodes.length)

assert.equal(childNodes.length, 1)
assert.equal(childNodes[0], childProxy._node)

const childProxy2 = createTestProxy2()
parentProxy.insertChild(childProxy2, childNodes.length)

assert.equal(childNodes.length, 2)
assert.equal(childNodes[1], childProxy2._node)
}

testAppend(createTestElement, createTestElement)
testAppend(createTestElement, createTestTextNode)
testAppend(createTestTextNode, createTestElement)
testAppend(createTestTextNode, createTestTextNode)
})

it(`inserts a child before another child`, () => {
const testInsertBefore = (createBeforeNode, createInsertedNode) => {
const parentProxy = ElementProxy.createElement(`div`)
const childNodes = parentProxy.childNodes()

const beforeNodeProxy = createBeforeNode()
parentProxy.insertChild(beforeNodeProxy, 0)

// Confirm assumptions
assert.equal(childNodes.length, 1)
assert.equal(childNodes[0], beforeNodeProxy._node)

const insertedNodeProxy = createInsertedNode()
parentProxy.insertChild(insertedNodeProxy, 0)

assert.equal(childNodes.length, 2)
assert.equal(childNodes[0], insertedNodeProxy._node)
assert.equal(childNodes[1], beforeNodeProxy._node)
}

testInsertBefore(createTestElement, createTestElement)
testInsertBefore(createTestElement, createTestTextNode)
testInsertBefore(createTestTextNode, createTestElement)
testInsertBefore(createTestTextNode, createTestTextNode)
})

it(`replaces a child`, () => {
const testReplaceChild = (createNodeProxy, createReplacementProxy) => {
const parentProxy = ElementProxy.createElement(`div`)
const childProxies = [createNodeProxy(), createNodeProxy(), createNodeProxy()]
const childNodes = parentProxy.childNodes()

childProxies.forEach((childProxy, i) => parentProxy.insertChild(childProxy, i))

// Confirm assumptions
assert.ok(childProxies.every((childProxy, i) => childProxy._node === childNodes[i]))

const replacementNode = createReplacementProxy()
parentProxy.replaceChild(replacementNode, 1)
const expectedChildProxies = [childProxies[0], replacementNode, childProxies[2]]
assert.ok(expectedChildProxies.every((expectedProxy, i) => expectedProxy._node === childNodes[i]))
}

testReplaceChild(createTestElement, createTestElement)
testReplaceChild(createTestElement, createTestTextNode)
testReplaceChild(createTestTextNode, createTestElement)
testReplaceChild(createTestTextNode, createTestTextNode)
})

it(`removes a child`, () => {
const testRemoveChild = (createTestProxy, createTestProxy2) => {
const parentProxy = ElementProxy.createElement(`div`)
const childNodes = parentProxy.childNodes()

const childProxy = createTestProxy()

parentProxy.insertChild(childProxy, childNodes.length)
assert.equal(childNodes.length, 1)
assert.equal(childNodes[0], childProxy._node)
parentProxy.removeChild(childProxy)
assert.equal(childNodes.length, 0)

const childProxy2 = createTestProxy2()

parentProxy.insertChild(childProxy, childNodes.length)
parentProxy.insertChild(childProxy2, childNodes.length)
assert.equal(childNodes.length, 2)

parentProxy.removeChild(childProxy)
assert.equal(childNodes.length, 1)
assert.equal(childNodes[0], childProxy2._node)

parentProxy.removeChild(childProxy2)
assert.equal(childNodes.length, 0)

parentProxy.insertChild(childProxy, childNodes.length)
parentProxy.insertChild(childProxy2, childNodes.length)
assert.equal(childNodes.length, 2)

parentProxy.removeChild(childProxy2)
assert.equal(childNodes.length, 1)
assert.equal(childNodes[0], childProxy._node)

parentProxy.removeChild(childProxy)
assert.equal(childNodes.length, 0)
}

testRemoveChild(createTestElement, createTestElement)
testRemoveChild(createTestElement, createTestTextNode)
testRemoveChild(createTestTextNode, createTestElement)
testRemoveChild(createTestTextNode, createTestTextNode)
})

it(`provides access to the child nodes of its DOM node`, () => {
const parentNode = document.createElement(`div`)
const expectedChildNodes = [
document.createElement(`a`),
document.createElement(`p`),
document.createTextNode(`nested-text`),
document.createElement(`span`),
]

expectedChildNodes.forEach(childNode => parentNode.appendChild(childNode))

const elementProxy = new ElementProxy(parentNode)
const actualChildNodes = elementProxy.childNodes()

assert.equal(actualChildNodes.length, expectedChildNodes.length)
expectedChildNodes.forEach((expectedChildNode, i) => {
assert.equal(actualChildNodes[i], expectedChildNode)
})
})

// NOTE: Tests still needed for ElementProxy.fromElement and get/set/remove attributes
})

0 comments on commit 60c89c3

Please sign in to comment.