This repository has been archived by the owner on Apr 25, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 32
Unit test ElementProxy and make related fixes - part one #104
Closed
brandonpayton
wants to merge
2
commits into
garbles:master
from
brandonpayton:add-elementproxy-unit-tests
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
/* @flow */ | ||
|
||
import document from 'global/document' | ||
import sinon from 'sinon/pkg/sinon' | ||
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(`creates a proxy from an existing element`) | ||
|
||
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 = () => {} | ||
|
||
elementProxy._emitMount = sinon.stub() | ||
elementProxy.emitMount(expectedMountCallback) | ||
assert.ok(elementProxy._emitMount.calledWith(expectedNode, expectedMountCallback)) | ||
|
||
elementProxy._emitUnmount = sinon.stub() | ||
elementProxy.emitUnmount(expectedUnmountCallback) | ||
assert.ok(elementProxy._emitUnmount.calledWith(expectedNode, 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) | ||
}) | ||
}) | ||
|
||
it(`sets, gets, and removes attributes according to descriptors`) | ||
it(`sets, gets, and removes properties according to descriptors`) | ||
it(`sets, gets, and removes event listeners according to descriptors`) | ||
}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do this? If
fn
is falsy, then the mount/unmount event won't fire. I'm not a huge fan of stubbing out another module this way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the signature should probably be
(fn: ?Function): void
. That's my fault.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was an attempt at letting the unit tests verify that
ElementProxy
is interacting with an underlying API so that we don't write the same tests forElementProxy
as we have formountable
. I think it is weak, but it is the best I came up with. What would you do?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to inject
emitMount
andemitUnmount
as part of the argument to the constructor and then test that they're called.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would all uses of
ElementProxy
be required to provide implementation foremitMount
andemitUnmount
? If so, that seems overly burdensome given that it is implementation detail that is not likely to be changed except for unit test.How would references to the injected implementations be maintained? Currently, I imagine it would be the same as this PR but injected via constructor rather than defaults
set
on the prototype. I may need another clue because my imagination is mostly producing what is here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I guess I just don't see the point of calling a function to make sure that another function is called when that function is just a stub. You might as well stub
emitMount
instead of_emitMount
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it seems a little silly. I was hoping to find a way to declare in the tests that
ElementProxy
needs to provideemitMount
andemitUnmount
while not unnecessarily testing what we're already testing formountable
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a little more thought, I think it makes sense to test mount events with
ElementProxy
sinceElementProxy
is responsible for both adding the listeners and emitting the events. This is in contrast tomountable
which is just responsible for emitting. My plan is to back out_emitMount
and_emitUnmount
and declare those tests to be added in a follow-up PR.Sound OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To shed a little more light on my original thinking, which I'm dropping anyway, the thought for stubbing
_emitMount
was to declare thatElementProxy
relies on something else for emitting and interacts with it according to the call signature. But I don't think it buys anything but a sort of legal satisfaction.