Convert DOMElementPool and specs to JS

This commit is contained in:
Nathan Sobo
2017-02-13 09:51:04 -07:00
committed by Antonio Scandurra
parent f7ca70419c
commit bb9d1f49c0
4 changed files with 132 additions and 115 deletions

View File

@@ -1,60 +0,0 @@
DOMElementPool = require '../src/dom-element-pool'
{contains} = require 'underscore-plus'
describe "DOMElementPool", ->
domElementPool = null
beforeEach ->
domElementPool = new DOMElementPool
it "builds DOM nodes, recycling them when they are freed", ->
[div, span1, span2, span3, span4, span5, textNode] = elements = [
domElementPool.buildElement("div")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildText("Hello world!")
]
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
span4.appendChild(textNode)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildText("another text"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(false)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(false)
expect(contains(elements, domElementPool.buildText("unexisting"))).toBe(false)
it "forgets free nodes after being cleared", ->
span = domElementPool.buildElement("span")
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.buildElement("span")).not.toBe(span)
expect(domElementPool.buildElement("div")).not.toBe(div)
it "throws an error when trying to free the same node twice", ->
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(div)
expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
it "throws an error when trying to free an invalid element", ->
expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()

View File

@@ -0,0 +1,64 @@
const DOMElementPool = require ('../src/dom-element-pool')
describe('DOMElementPool', function () {
let domElementPool
beforeEach(() => { domElementPool = new DOMElementPool() })
it('builds DOM nodes, recycling them when they are freed', function () {
let elements
const [div, span1, span2, span3, span4, span5, textNode] = Array.from(elements = [
domElementPool.buildElement('div'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildText('Hello world!')
])
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
span4.appendChild(textNode)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(elements.includes(domElementPool.buildElement('div'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildText('another text'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('div'))).toBe(false)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(false)
expect(elements.includes(domElementPool.buildText('unexisting'))).toBe(false)
})
it('forgets free nodes after being cleared', function () {
const span = domElementPool.buildElement('span')
const div = domElementPool.buildElement('div')
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.buildElement('span')).not.toBe(span)
expect(domElementPool.buildElement('div')).not.toBe(div)
})
it('throws an error when trying to free the same node twice', function () {
const div = domElementPool.buildElement('div')
domElementPool.freeElementAndDescendants(div)
expect(() => domElementPool.freeElementAndDescendants(div)).toThrow()
})
it('throws an error when trying to free an invalid element', function () {
expect(() => domElementPool.freeElementAndDescendants(null)).toThrow()
expect(() => domElementPool.freeElementAndDescendants(undefined)).toThrow()
})
})

View File

@@ -1,55 +0,0 @@
module.exports =
class DOMElementPool
constructor: ->
@freeElementsByTagName = {}
@freedElements = new Set
clear: ->
@freedElements.clear()
for tagName, freeElements of @freeElementsByTagName
freeElements.length = 0
return
build: (tagName, factory, reset) ->
element = @freeElementsByTagName[tagName]?.pop()
element ?= factory()
reset(element)
@freedElements.delete(element)
element
buildElement: (tagName, className) ->
factory = -> document.createElement(tagName)
reset = (element) ->
delete element.dataset[dataId] for dataId of element.dataset
element.removeAttribute("style")
if className?
element.className = className
else
element.removeAttribute("class")
@build(tagName, factory, reset)
buildText: (textContent) ->
factory = -> document.createTextNode(textContent)
reset = (element) -> element.textContent = textContent
@build("#text", factory, reset)
freeElementAndDescendants: (element) ->
@free(element)
@freeDescendants(element)
freeDescendants: (element) ->
for descendant in element.childNodes by -1
@free(descendant)
@freeDescendants(descendant)
return
free: (element) ->
throw new Error("The element cannot be null or undefined.") unless element?
throw new Error("The element has already been freed!") if @freedElements.has(element)
tagName = element.nodeName.toLowerCase()
@freeElementsByTagName[tagName] ?= []
@freeElementsByTagName[tagName].push(element)
@freedElements.add(element)
element.remove()

68
src/dom-element-pool.js Normal file
View File

@@ -0,0 +1,68 @@
module.exports =
class DOMElementPool {
constructor () {
this.freeElementsByTagName = {}
this.freedElements = new Set()
}
clear () {
this.freedElements.clear()
for (let tagName in this.freeElementsByTagName) {
const freeElements = this.freeElementsByTagName[tagName]
freeElements.length = 0
}
}
build (tagName, factory, reset) {
let element = this.freeElementsByTagName[tagName] ? this.freeElementsByTagName[tagName].pop() : null
if (!element) { element = factory() }
reset(element)
this.freedElements.delete(element)
return element
}
buildElement (tagName, className) {
const factory = () => document.createElement(tagName)
const reset = function (element) {
for (let dataId in element.dataset) { delete element.dataset[dataId] }
element.removeAttribute('style')
if (className != null) {
element.className = className
} else {
element.removeAttribute('class')
}
}
return this.build(tagName, factory, reset)
}
buildText (textContent) {
const factory = () => document.createTextNode(textContent)
const reset = element => { element.textContent = textContent }
return this.build('#text', factory, reset)
}
freeElementAndDescendants (element) {
this.free(element)
return this.freeDescendants(element)
}
freeDescendants (element) {
for (let i = element.childNodes.length - 1; i >= 0; i--) {
const descendant = element.childNodes[i]
this.free(descendant)
this.freeDescendants(descendant)
}
}
free (element) {
if (element == null) { throw new Error('The element cannot be null or undefined.') }
if (this.freedElements.has(element)) { throw new Error('The element has already been freed!') }
const tagName = element.nodeName.toLowerCase()
if (this.freeElementsByTagName[tagName] == null) { this.freeElementsByTagName[tagName] = [] }
this.freeElementsByTagName[tagName].push(element)
this.freedElements.add(element)
return element.remove()
}
}