_           = require 'lodash'
ViewModel = require '../../../view/ViewModel'
Model       = require 'stout-core/model/Model'


class TestModel extends Model
  @property 'first'
  @property 'last'
  constructor: -> super arguments...



describe 'view/ViewModel', ->

  m = v = s1 = null

  listHTML =
    "<ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
    </ul>"

  beforeEach ->
    v = new ViewModel()
    s1 = sinon.spy()
    m = new TestModel first: 'John', last: 'Smith'


  describe '#constructor()', ->

    it 'passes initialization parameters to parent View class', ->
      tmpl = ->
      model = new TestModel
      v = new ViewModel(tmpl, model)
      expect(v.model).to.equal model
      expect(v.template).to.equal tmpl

    it 'automatically creates a DOM node for the view', ->
      expect(v.root.nodeName).to.equal 'DIV'

    it 'registers the `click:anchor` event', ->
      expect(v.eventRegistered 'click').to.be.true

    it 'automatically re-renders the view when the model changes', ->
      f = (t) ->
        expect(t.model.first).to.equal m.first
        expect(t.model.last).to.equal m.last
      v = new ViewModel f, m
      v.render()
      v.on 'render', s1
      expect(s1.called).to.be.false
      m.first = 'Jane'
      expect(s1.calledOnce).to.be.true
      m.last = 'Doe'
      expect(s1.calledTwice).to.be.true

    it 'does not auto re-render if `renderOnChange` option set to false', ->
      v = new ViewModel (->), m, {renderOnChange: false}
      v.render()
      v.on 'render', s1
      m.first = 'Jane'
      m.last = 'Doe'
      expect(s1.called).to.be.false


  for prop in ['classes', 'prefixedClasses']
    describe "#classes and #prefixedClasses common functionality", ->
      describe "##{prop} getter", ->
        it 'returns a classList-like object', ->
          cl = v[prop]
          expect(cl).to.respondTo 'add'
          expect(cl).to.respondTo 'remove'
          expect(cl).to.respondTo 'contains'
          expect(cl).to.respondTo 'toggle'

        it 'is array-like and lists all classes on the root object', ->
          expect(v[prop].indexOf 'test-class').to.equal -1
          v.root.classList.add 'test-class'
          expect(v[prop].indexOf 'test-class').to.equal 0

      describe  "##{prop} setter", ->

        it 'adds a class to the view\'s root node', ->
          expect(v.root.classList.contains 'test-class').to.be.false
          v[prop] = 'test-class'
          expect(v.root.classList.contains 'test-class').to.be.true

        it 'adds space-separated classes to the view\'s root node', ->
          expect(v.root.classList.contains 'test-1').to.be.false
          expect(v.root.classList.contains 'test-2').to.be.false
          expect(v.root.classList.contains 'test-3').to.be.false
          v[prop] = 'test-1 test-2 test-3'
          expect(v.root.classList.contains 'test-1').to.be.true
          expect(v.root.classList.contains 'test-2').to.be.true
          expect(v.root.classList.contains 'test-3').to.be.true

        it 'removes existing classes', ->
          expect(v.root.classList.length).to.equal 0
          v[prop] = 'test-1 test-2'
          expect(v.root.classList.length).to.equal 2
          v[prop] = 'test-3 test-4'
          expect(v.root.classList.length).to.equal 2
          expect(v.root.classList.contains 'test-1').to.be.false
          expect(v.root.classList.contains 'test-2').to.be.false
          expect(v.root.classList.contains 'test-3').to.be.true
          expect(v.root.classList.contains 'test-4').to.be.true


  describe '#events', ->

    describe '#events getter', ->

      it 'returns recently set events object', ->
        evts = {}
        v.events = evts
        expect(v.events).to.equal evts

    describe '#events setter', ->

      it 'registers the passed event names', ->
        expect(v.eventRegistered 'e1').to.be.false
        expect(v.eventRegistered 'e2').to.be.false
        expect(v.eventRegistered 'e3').to.be.false
        v.events =
          'e1': 'click #fake-id'
          'e2 e3': 'click #fake-id'
        expect(v.eventRegistered 'e1').to.be.true
        expect(v.eventRegistered 'e2').to.be.true
        expect(v.eventRegistered 'e3').to.be.true

      it 'retains registered events if passed twice', ->
        expect(v.eventRegistered 'e1').to.be.false
        expect(v.eventRegistered 'e2').to.be.false
        expect(v.eventRegistered 'e3').to.be.false
        v.events =
          'e1': 'click #fake-id'
          'e2 e3': 'click #fake-id'
        v.events =
          'e1': 'click #fake-id'
          'e2': 'click #fake-id'
        expect(v.eventRegistered 'e1').to.be.true
        expect(v.eventRegistered 'e2').to.be.true
        expect(v.eventRegistered 'e3').to.be.false

      it 'deregisters events missing from subsequent sets', ->
        v.events =
          'e1': 'click #fake-id'
          'e2 e3': 'click #fake-id'
        expect(v.eventRegistered 'e1').to.be.true
        expect(v.eventRegistered 'e2').to.be.true
        expect(v.eventRegistered 'e3').to.be.true
        v.events =
          'e4': 'click #fake-id'
          'e5 e6': 'click #fake-id'
        expect(v.eventRegistered 'e1').to.be.false
        expect(v.eventRegistered 'e2').to.be.false
        expect(v.eventRegistered 'e3').to.be.false
        expect(v.eventRegistered 'e4').to.be.true
        expect(v.eventRegistered 'e5').to.be.true
        expect(v.eventRegistered 'e6').to.be.true

    it 'binds event listeners and fires proxied events', ->
      v.events =
        'test-emit-1': 'test-event li'
        'test-emit-2': 'test-event li'
      s2 = sinon.spy()
      v.template = -> listHTML
      v.on 'test-emit-1', s1
      v.on 'test-emit-2', s2
      v.render()
      v.select 'li', ->
        this.dispatchEvent new Event 'test-event'
      expect(s1.calledOnce).to.be.true
      expect(s2.calledOnce).to.be.true


  describe '#prefixedClasses', ->

    describe '#add()', ->

      it 'prefixes added classes with the configured prefix', ->
        v.prefix = 't-'
        expect(v.classes.contains 't-test').to.be.false
        v.prefixedClasses.add 'test'
        expect(v.classes.contains 't-test').to.be.true

    describe '#remove()', ->

      it 'removes classes with the prefix', ->
        v.prefix = 't-'
        v.prefixedClasses.add 'test'
        expect(v.classes.contains 't-test').to.be.true
        v.prefixedClasses.remove 'test'
        expect(v.classes.contains 't-test').to.be.false

    describe '#contains()', ->

      it 'adds the prefix when checking for classes', ->
        v.prefix = 't-'
        expect(v.prefixedClasses.contains 'test').to.be.false
        v.prefixedClasses.add 'test'
        expect(v.prefixedClasses.contains 'test').to.be.true

    describe '#toggle()', ->

      it 'toggles classes with the prefix', ->
        v.prefix = 't-'
        expect(v.prefixedClasses.contains 'test').to.be.false
        expect(v.classes.contains 't-test').to.be.false
        v.prefixedClasses.toggle 'test'
        expect(v.prefixedClasses.contains 'test').to.be.true
        expect(v.classes.contains 't-test').to.be.true
        v.prefixedClasses.toggle 'test'
        expect(v.prefixedClasses.contains 'test').to.be.false
        expect(v.classes.contains 't-test').to.be.false


  describe '#root', ->

    it 'sets the root element id', ->
      v.id = 'test'
      expect(v.root.id).to.equal 'test'

    it 'sets the id of the root element after classes are set', ->
      v.classes = 'test-1 test-2'
      expect(v.root.id).to.equal ''
      v.id = 'test'
      expect(v.root.id).to.equal 'test'

    it 'sets the id of the root element before classes are set', ->
      v.id = 'test'
      expect(v.root.id).to.equal 'test'
      v.classes = 'test-1 test-2'
      expect(v.root.id).to.equal 'test'


  describe '#tagName', ->

    it 'sets the HTML tag name of the root element', ->
      v.tagName = 'blockquote'
      expect(v.root.tagName).to.equal 'BLOCKQUOTE'
      v.tagName = 'article'
      expect(v.root.tagName).to.equal 'ARTICLE'

    it 'recreates the root node if changed', ->
      o = v.root
      v.tagName = 'span'
      expect(v.root).to.not.equal o

    it 'copies over class names when recreating the root node', ->
      v.classes = 'test-1 test-2'
      expect(v.classes.contains 'test-1').to.be.true
      expect(v.classes.contains 'test-2').to.be.true
      expect(v.classes.contains 'test-3').to.be.false
      v.tagName = 'i'
      expect(v.classes.contains 'test-1').to.be.true
      expect(v.classes.contains 'test-2').to.be.true
      expect(v.classes.contains 'test-3').to.be.false

    it 'moves child nodes to new root', ->
      a = document.createElement 'a'
      b = document.createElement 'a'
      c = document.createElement 'a'
      v.root.appendChild a
      v.root.appendChild b
      v.root.appendChild c
      expect(a.parentNode).to.equal v.root
      expect(b.parentNode).to.equal v.root
      expect(c.parentNode).to.equal v.root
      oldRoot = v.root
      v.tagName = 'span'
      expect(v.root).to.not.equal oldRoot
      expect(a.parentNode).to.equal v.root
      expect(b.parentNode).to.equal v.root
      expect(c.parentNode).to.equal v.root

    it 'attaches new node to previous node\'s parent', ->
      p = document.createElement 'div'
      v.parent = p
      v.template = -> ''
      v.render()
      expect(v.root.parentNode).to.equal p
      v.tagName = 'span'
      expect(v.root.parentNode).to.equal p

    it 'can be initialized', ->
      v = new ViewModel null, null, null, tagName: 'blockquote'
      expect(v.tagName).to.equal 'BLOCKQUOTE'


  describe '#select()', ->

    beforeEach ->
      v = new ViewModel -> listHTML
      v.render()

    it 'returns the first matching HTMLElement', ->
      e = v.select 'li'
      expect(e).to.exist
      expect(e.textContent).to.equal '1'
      expect(e.tagName).to.equal 'LI'



  describe '#selectAll()', ->

    beforeEach ->
      v = new ViewModel -> listHTML

    it 'returns an HTMLNodeList of the matching elements', ->
      v.render()
      e = v.selectAll 'li'
      expect(e).to.exist
      expect(e.length).to.equal 4

    it 'iterates the callback over each matching element', ->
      count = 0
      v.selectAll 'li', ->
        count++
      expect(count).to.equal 0
      v.render()
      v.selectAll 'li', ->
        count++
      expect(count).to.equal 4



  describe '#render()', ->

    html = '<a href="#someplace">test anchor</a>'

    beforeEach ->
      v = new ViewModel -> html

    it 'replaces the HTML contents of the root node', ->
      expect(v.root.innerHTML).to.equal ''
      v.render()
      expect(v.root.innerHTML).to.equal html

    it 'attaches a click listener to anchors without targets', (done) ->
      v.on 'click:anchor', (evt) ->
        assert.match evt.data, /someplace/
        done()
      v.render()
      a = v.root.querySelector 'a'
      a.dispatchEvent new Event 'click'

    it 'does not attach click listeners to anchors with targets', (done) ->
      html = '<a target="_blank" href="/">test anchor</a>'
      t = -> return html
      v = new ViewModel t
      v.render()
      a = v.root.querySelector 'a'
      a.dispatchEvent new Event 'click'
      setTimeout done, 20

    it 'attaches rendered root to a string-selector parent', ->
      p = document.createElement 'div'
      p.id = 'test-parent'
      document.body.appendChild p
      v.parent = '#test-parent'
      v.render()
      expect(p.firstChild).to.equal v.root


  describe '#unrender()', ->

    it 'empties the root element and removes root from parent', ->
      v.parent = 'body'
      v.template = -> listHTML
      v.render()
      expect(v.root.parentNode).to.equal document.body
      expect(v.root.children.length).to.equal 1
      expect(v.root.children[0].tagName).to.equal 'UL'
      v.unrender()
      expect(v.root.parentNode).to.equal null
      expect(v.root.children.length).to.equal 0


  describe '#empty()', ->

    html = '<div id="someDiv"></div>'

    beforeEach ->
      v = new ViewModel -> html
      v.render()

    it 'sets the `rendered` property to `false`', ->
      expect(v.rendered).to.be.true
      v.empty()
      expect(v.rendered).to.be.false
