mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-14 15:20:28 +03:00
22ca33a3b1
By adding this extension to ViewRegistry::getView we're paving the way for Etch-like view frameworks which promotes the usage of plain objects and classes with an element property which is an instance of HTMLElement.
245 lines
8.4 KiB
CoffeeScript
245 lines
8.4 KiB
CoffeeScript
ViewRegistry = require '../src/view-registry'
|
|
{View} = require '../src/space-pen-extensions'
|
|
|
|
describe "ViewRegistry", ->
|
|
registry = null
|
|
|
|
beforeEach ->
|
|
registry = new ViewRegistry
|
|
|
|
afterEach ->
|
|
registry.clearDocumentRequests()
|
|
|
|
describe "::getView(object)", ->
|
|
describe "when passed a DOM node", ->
|
|
it "returns the given DOM node", ->
|
|
node = document.createElement('div')
|
|
expect(registry.getView(node)).toBe node
|
|
|
|
describe "when passed a SpacePen view", ->
|
|
it "returns the root node of the view with a .spacePenView property pointing at the SpacePen view", ->
|
|
class TestView extends View
|
|
@content: -> @div "Hello"
|
|
|
|
view = new TestView
|
|
node = registry.getView(view)
|
|
expect(node.textContent).toBe "Hello"
|
|
expect(node.spacePenView).toBe view
|
|
|
|
describe "when passed an object with an element property", ->
|
|
it "returns the element property if it's an instance of HTMLElement", ->
|
|
class TestComponent
|
|
constructor: -> @element = document.createElement('div')
|
|
|
|
component = new TestComponent
|
|
expect(registry.getView(component)).toBe component.element
|
|
|
|
describe "when passed a model object", ->
|
|
describe "when a view provider is registered matching the object's constructor", ->
|
|
it "constructs a view element and assigns the model on it", ->
|
|
class TestModel
|
|
|
|
class TestModelSubclass extends TestModel
|
|
|
|
class TestView
|
|
initialize: (@model) -> this
|
|
|
|
model = new TestModel
|
|
|
|
registry.addViewProvider TestModel, (model) ->
|
|
new TestView().initialize(model)
|
|
|
|
view = registry.getView(model)
|
|
expect(view instanceof TestView).toBe true
|
|
expect(view.model).toBe model
|
|
|
|
subclassModel = new TestModelSubclass
|
|
view2 = registry.getView(subclassModel)
|
|
expect(view2 instanceof TestView).toBe true
|
|
expect(view2.model).toBe subclassModel
|
|
|
|
describe "when no view provider is registered for the object's constructor", ->
|
|
describe "when the object has a .getViewClass() method", ->
|
|
it "builds an instance of the view class with the model, then returns its root node with a __spacePenView property pointing at the view", ->
|
|
class TestView extends View
|
|
@content: (model) -> @div model.name
|
|
initialize: (@model) ->
|
|
|
|
class TestModel
|
|
constructor: (@name) ->
|
|
getViewClass: -> TestView
|
|
|
|
model = new TestModel("hello")
|
|
node = registry.getView(model)
|
|
|
|
expect(node.textContent).toBe "hello"
|
|
view = node.spacePenView
|
|
expect(view instanceof TestView).toBe true
|
|
expect(view.model).toBe model
|
|
|
|
# returns the same DOM node for repeated calls
|
|
expect(registry.getView(model)).toBe node
|
|
|
|
describe "when the object has no .getViewClass() method", ->
|
|
it "throws an exception", ->
|
|
expect(-> registry.getView(new Object)).toThrow()
|
|
|
|
describe "::addViewProvider(providerSpec)", ->
|
|
it "returns a disposable that can be used to remove the provider", ->
|
|
class TestModel
|
|
class TestView
|
|
initialize: (@model) -> this
|
|
|
|
disposable = registry.addViewProvider TestModel, (model) ->
|
|
new TestView().initialize(model)
|
|
|
|
expect(registry.getView(new TestModel) instanceof TestView).toBe true
|
|
disposable.dispose()
|
|
expect(-> registry.getView(new TestModel)).toThrow()
|
|
|
|
describe "::updateDocument(fn) and ::readDocument(fn)", ->
|
|
frameRequests = null
|
|
|
|
beforeEach ->
|
|
frameRequests = []
|
|
spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn)
|
|
|
|
it "performs all pending writes before all pending reads on the next animation frame", ->
|
|
events = []
|
|
|
|
registry.updateDocument -> events.push('write 1')
|
|
registry.readDocument -> events.push('read 1')
|
|
registry.readDocument -> events.push('read 2')
|
|
registry.updateDocument -> events.push('write 2')
|
|
|
|
expect(events).toEqual []
|
|
|
|
expect(frameRequests.length).toBe 1
|
|
frameRequests[0]()
|
|
expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2']
|
|
|
|
frameRequests = []
|
|
events = []
|
|
disposable = registry.updateDocument -> events.push('write 3')
|
|
registry.updateDocument -> events.push('write 4')
|
|
registry.readDocument -> events.push('read 3')
|
|
|
|
disposable.dispose()
|
|
|
|
expect(frameRequests.length).toBe 1
|
|
frameRequests[0]()
|
|
expect(events).toEqual ['write 4', 'read 3']
|
|
|
|
it "performs writes requested from read callbacks in the same animation frame", ->
|
|
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
|
|
spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
|
|
events = []
|
|
|
|
registry.pollDocument -> events.push('poll')
|
|
registry.pollAfterNextUpdate()
|
|
registry.updateDocument -> events.push('write 1')
|
|
registry.readDocument ->
|
|
registry.updateDocument -> events.push('write from read 1')
|
|
events.push('read 1')
|
|
registry.readDocument ->
|
|
registry.updateDocument -> events.push('write from read 2')
|
|
events.push('read 2')
|
|
registry.updateDocument -> events.push('write 2')
|
|
|
|
expect(frameRequests.length).toBe 1
|
|
frameRequests[0]()
|
|
expect(frameRequests.length).toBe 1
|
|
|
|
expect(events).toEqual [
|
|
'write 1'
|
|
'write 2'
|
|
'read 1'
|
|
'read 2'
|
|
'poll'
|
|
'write from read 1'
|
|
'write from read 2'
|
|
]
|
|
|
|
it "pauses DOM polling when reads or writes are pending", ->
|
|
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
|
|
spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
|
|
events = []
|
|
|
|
registry.pollDocument -> events.push('poll')
|
|
registry.updateDocument -> events.push('write')
|
|
registry.readDocument -> events.push('read')
|
|
|
|
window.dispatchEvent(new UIEvent('resize'))
|
|
expect(events).toEqual []
|
|
|
|
frameRequests[0]()
|
|
expect(events).toEqual ['write', 'read', 'poll']
|
|
|
|
window.dispatchEvent(new UIEvent('resize'))
|
|
expect(events).toEqual ['write', 'read', 'poll', 'poll']
|
|
|
|
it "polls the document after updating when ::pollAfterNextUpdate() has been called", ->
|
|
events = []
|
|
registry.pollDocument -> events.push('poll')
|
|
registry.updateDocument -> events.push('write')
|
|
registry.readDocument -> events.push('read')
|
|
frameRequests.shift()()
|
|
expect(events).toEqual ['write', 'read']
|
|
|
|
events = []
|
|
registry.pollAfterNextUpdate()
|
|
registry.updateDocument -> events.push('write')
|
|
registry.readDocument -> events.push('read')
|
|
frameRequests.shift()()
|
|
expect(events).toEqual ['write', 'read', 'poll']
|
|
|
|
describe "::pollDocument(fn)", ->
|
|
[testElement, testStyleSheet, disposable1, disposable2, events] = []
|
|
|
|
beforeEach ->
|
|
testElement = document.createElement('div')
|
|
testStyleSheet = document.createElement('style')
|
|
testStyleSheet.textContent = 'body {}'
|
|
jasmineContent = document.getElementById('jasmine-content')
|
|
jasmineContent.appendChild(testElement)
|
|
jasmineContent.appendChild(testStyleSheet)
|
|
|
|
events = []
|
|
disposable1 = registry.pollDocument -> events.push('poll 1')
|
|
disposable2 = registry.pollDocument -> events.push('poll 2')
|
|
|
|
it "calls all registered polling functions after document or stylesheet changes until they are disabled via a returned disposable", ->
|
|
jasmine.useRealClock()
|
|
expect(events).toEqual []
|
|
|
|
testElement.style.width = '400px'
|
|
|
|
waitsFor "events to occur in response to DOM mutation", -> events.length > 0
|
|
|
|
runs ->
|
|
expect(events).toEqual ['poll 1', 'poll 2']
|
|
events.length = 0
|
|
|
|
testStyleSheet.textContent = 'body {color: #333;}'
|
|
|
|
waitsFor "events to occur in reponse to style sheet mutation", -> events.length > 0
|
|
|
|
runs ->
|
|
expect(events).toEqual ['poll 1', 'poll 2']
|
|
events.length = 0
|
|
|
|
disposable1.dispose()
|
|
testElement.style.color = '#fff'
|
|
|
|
waitsFor "more events to occur in response to DOM mutation", -> events.length > 0
|
|
|
|
runs ->
|
|
expect(events).toEqual ['poll 2']
|
|
|
|
it "calls all registered polling functions when the window resizes", ->
|
|
expect(events).toEqual []
|
|
|
|
window.dispatchEvent(new UIEvent('resize'))
|
|
|
|
expect(events).toEqual ['poll 1', 'poll 2']
|