describe('hx-trigger attribute', function() {
  beforeEach(function() {
    this.server = sinon.fakeServer.create()
    clearWorkArea()
  })
  afterEach(function() {
    this.server.restore()
    clearWorkArea()
  })

  it('non-default value works', function() {
    this.server.respondWith('GET', '/test', 'Clicked!')
    var form = make('<form hx-get="/test" hx-trigger="click">Click Me!</form>')
    form.click()
    form.innerHTML.should.equal('Click Me!')
    this.server.respond()
    form.innerHTML.should.equal('Clicked!')
  })

  it('changed modifier works', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input = make('<input hx-trigger="click changed" hx-target="#d1" hx-get="/test"/>')
    var div = make('<div id="d1"></div>')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input.value = 'bar'
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
  })

  it('changed modifier works along from clause with single input', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input = make('<input type="text"/>')
    make('<div hx-trigger="click changed from:input" hx-target="#d1" hx-get="/test"></div>')
    var div = make('<div id="d1"></div>')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input.value = 'bar'
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
  })

  // This test and the next one should be kept in sync.
  it('changed modifier works along from clause with two inputs', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input1 = make('<input type="text"/>')
    var input2 = make('<input type="text"/>')
    make('<div hx-trigger="click changed from:input" hx-target="#d1" hx-get="/test"></div>')
    var div = make('<div id="d1"></div>')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')

    input1.value = 'bar'
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')

    input2.value = 'foo'
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  // This test and the previous one should be kept in sync.
  it('changed modifier counts each triggerspec separately', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input1 = make('<input type="text"/>')
    var input2 = make('<input type="text"/>')
    make('<div hx-trigger="click changed from:input" hx-target="#d1" hx-get="/test"></div>')
    make('<div hx-trigger="click changed from:input" hx-target="#d1" hx-get="/test"></div>')
    var div = make('<div id="d1"></div>')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')

    input1.value = 'bar'
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')

    input2.value = 'foo'
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 4')
  })

  it('separate changed modifier works along from clause with two inputs', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input1 = make('<input type="text"/>')
    var input2 = make('<input type="text"/>')
    make('<div hx-trigger="click changed from:input:nth-child(1), click changed from:input:nth-child(2)" hx-target="#d1" hx-get="/test"></div>')
    var div = make('<div id="d1"></div>')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')

    input1.value = 'bar'
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('')
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')

    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')

    input2.value = 'foo'
    input1.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input2.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  it('once modifier works', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input = make('<input hx-trigger="click once" hx-target="#d1" hx-get="/test" value="foo"/>')
    var div = make('<div id="d1"></div>')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.value = 'bar'
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
  })

  it('once modifier works with multiple triggers', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var input = make('<input hx-trigger="click once, foo" hx-target="#d1" hx-get="/test" value="foo"/>')
    var div = make('<div id="d1"></div>')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.value = 'bar'
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    input.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    htmx.trigger(input, 'foo')
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  it('polling works', function(complete) {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      if (requests > 5) {
        complete()
        // cancel polling with a
        xhr.respond(286, {}, 'Requests: ' + requests)
      } else {
        xhr.respond(200, {}, 'Requests: ' + requests)
      }
    })
    this.server.autoRespond = true
    this.server.autoRespondAfter = 0
    make('<div hx-trigger="every 10ms" hx-get="/test"/>')
  })

  it('non-default value works w/ data-* prefix', function() {
    this.server.respondWith('GET', '/test', 'Clicked!')
    var form = make('<form data-hx-get="/test" data-hx-trigger="click">Click Me!</form>')
    form.click()
    form.innerHTML.should.equal('Click Me!')
    this.server.respond()
    form.innerHTML.should.equal('Clicked!')
  })

  it('works with multiple events', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div = make('<div hx-trigger="load,click" hx-get="/test">Requests: 0</div>')
    div.innerHTML.should.equal('Requests: 0')
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    div.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  it('parses spec strings', function() {
    var specExamples = {
      '': [{ trigger: 'click' }],
      'every 1s': [{ trigger: 'every', pollInterval: 1000 }],
      'every 0s': [{ trigger: 'every', pollInterval: 0 }],
      'every 0ms': [{ trigger: 'every', pollInterval: 0 }],
      click: [{ trigger: 'click' }],
      customEvent: [{ trigger: 'customEvent' }],
      'event changed': [{ trigger: 'event', changed: true }],
      'event once': [{ trigger: 'event', once: true }],
      'event throttle:1s': [{ trigger: 'event', throttle: 1000 }],
      'event throttle:0s': [{ trigger: 'event', throttle: 0 }],
      'event throttle:0ms': [{ trigger: 'event', throttle: 0 }],
      'event throttle:1s, foo': [{ trigger: 'event', throttle: 1000 }, { trigger: 'foo' }],
      'event delay:1s': [{ trigger: 'event', delay: 1000 }],
      'event delay:1s, foo': [{ trigger: 'event', delay: 1000 }, { trigger: 'foo' }],
      'event delay:0s, foo': [{ trigger: 'event', delay: 0 }, { trigger: 'foo' }],
      'event delay:0ms, foo': [{ trigger: 'event', delay: 0 }, { trigger: 'foo' }],
      'event changed once delay:1s': [{ trigger: 'event', changed: true, once: true, delay: 1000 }],
      'event1,event2': [{ trigger: 'event1' }, { trigger: 'event2' }],
      'event1, event2': [{ trigger: 'event1' }, { trigger: 'event2' }],
      'event1 once, event2 changed': [{ trigger: 'event1', once: true }, { trigger: 'event2', changed: true }],
      'event1,': [{ trigger: 'event1' }],
      '  ': [{ trigger: 'click' }]
    }

    for (var specString in specExamples) {
      var div = make("<div hx-trigger='" + specString + "'></div>")
      var spec = htmx._('getTriggerSpecs')(div)
      spec.should.deep.equal(specExamples[specString], 'Found : ' + JSON.stringify(spec) + ', expected : ' + JSON.stringify(specExamples[specString]) + ' for spec: ' + specString)
    }
  })

  it('sets default trigger for forms', function() {
    var form = make('<form></form>')
    var spec = htmx._('getTriggerSpecs')(form)
    spec.should.deep.equal([{ trigger: 'submit' }])
  })

  it('sets default trigger for form elements', function() {
    var form = make('<input></input>')
    var spec = htmx._('getTriggerSpecs')(form)
    spec.should.deep.equal([{ trigger: 'change' }])
  })

  it('filters properly with false filter spec', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>')
    form.click()
    form.innerHTML.should.equal('Not Called')
    var event = htmx._('makeEvent')('evt')
    form.dispatchEvent(event)
    this.server.respond()
    form.innerHTML.should.equal('Not Called')
  })

  it('filters properly with true filter spec', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var form = make('<form hx-get="/test" hx-trigger="evt[foo]">Not Called</form>')
    form.click()
    form.innerHTML.should.equal('Not Called')
    var event = htmx._('makeEvent')('evt')
    event.foo = true
    form.dispatchEvent(event)
    this.server.respond()
    form.innerHTML.should.equal('Called!')
  })

  it('filters properly compound filter spec', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="evt[foo&&bar]">Not Called</div>')
    var event = htmx._('makeEvent')('evt')
    event.foo = true
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Not Called')
    event.bar = true
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Called!')
  })

  it('can refer to target element in condition', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="evt[target.classList.contains(\'doIt\')]">Not Called</div>')
    var event = htmx._('makeEvent')('evt')
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Not Called')
    div.classList.add('doIt')
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Called!')
  })

  it('can refer to target element in condition w/ equality', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="evt[target.id==\'foo\']">Not Called</div>')
    var event = htmx._('makeEvent')('evt')
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Not Called')
    div.id = 'foo'
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Called!')
  })

  it('negative condition', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="evt[!target.classList.contains(\'disabled\')]">Not Called</div>')
    div.classList.add('disabled')
    var event = htmx._('makeEvent')('evt')
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Not Called')
    div.classList.remove('disabled')
    div.dispatchEvent(event)
    this.server.respond()
    div.innerHTML.should.equal('Called!')
  })

  it('global function call works', function() {
    window.globalFun = function(evt) {
      return evt.bar
    }
    try {
      this.server.respondWith('GET', '/test', 'Called!')
      var div = make('<div hx-get="/test" hx-trigger="evt[globalFun(event)]">Not Called</div>')
      var event = htmx._('makeEvent')('evt')
      event.bar = false
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Not Called')
      event.bar = true
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Called!')
    } finally {
      delete window.globalFun
    }
  })

  it('global property event filter works', function() {
    window.foo = {
      bar: false
    }
    try {
      this.server.respondWith('GET', '/test', 'Called!')
      var div = make('<div hx-get="/test" hx-trigger="evt[foo.bar]">Not Called</div>')
      var event = htmx._('makeEvent')('evt')
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Not Called')
      foo.bar = true
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Called!')
    } finally {
      delete window.foo
    }
  })

  it('global variable filter works', function() {
    try {
      this.server.respondWith('GET', '/test', 'Called!')
      var div = make('<div hx-get="/test" hx-trigger="evt[foo]">Not Called</div>')
      var event = htmx._('makeEvent')('evt')
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Not Called')
      window.foo = true
      div.dispatchEvent(event)
      this.server.respond()
      div.innerHTML.should.equal('Called!')
    } finally {
      delete window.foo
    }
  })

  it('can filter polling', function(complete) {
    this.server.respondWith('GET', '/test', 'Called!')
    window.foo = false
    var div = make('<div hx-get="/test" hx-trigger="every 5ms[foo]">Not Called</div>')
    var div2 = make('<div hx-get="/test" hx-trigger="every 5ms">Not Called</div>')
    this.server.autoRespond = true
    this.server.autoRespondAfter = 0
    setTimeout(function() {
      div.innerHTML.should.equal('Not Called')
      div2.innerHTML.should.equal('Called!')
      delete window.foo
      complete()
    }, 100)
  })

  it('bad condition issues error', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="evt[a.b]">Not Called</div>')
    var errorEvent = null
    var handler = htmx.on('htmx:eventFilter:error', function(event) {
      errorEvent = event
    })
    try {
      var event = htmx._('makeEvent')('evt')
      div.dispatchEvent(event)
      should.not.equal(null, errorEvent)
      should.not.equal(null, errorEvent.detail.source)
      console.log(errorEvent.detail.source)
    } finally {
      htmx.off('htmx:eventFilter:error', handler)
    }
  })

  it('filters properly with true for empty condition', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var form = make('<form hx-get="/test" hx-trigger="evt[]">Not Called</form>')
    form.click()
    form.innerHTML.should.equal('Not Called')
    var event = htmx._('makeEvent')('evt')
    form.dispatchEvent(event)
    this.server.respond()
    form.innerHTML.should.equal('Called!')
  })

  it('syntax error in condition issues error', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var errorEvent = null
    var handler = htmx.on('htmx:syntax:error', function(event) {
      errorEvent = event
    })
    var form = make('<form hx-get="/test" hx-trigger="evt[{]">Not Called</form>')
    try {
      var event = htmx._('makeEvent')('evt')
      form.dispatchEvent(event)
      should.not.equal(null, errorEvent)
      should.not.equal(null, errorEvent.detail.source)
      console.log(errorEvent.detail.source)
    } finally {
      htmx.off('htmx:syntax:error', handler)
    }
  })

  it('filters properly with condition containing square backets', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var form = make('<form hx-get="/test" hx-trigger="evt[foo[0]]">Not Called</form>')
    form.click()
    form.innerHTML.should.equal('Not Called')
    var event = htmx._('makeEvent')('evt')
    event.foo = [true]
    form.dispatchEvent(event)
    this.server.respond()
    form.innerHTML.should.equal('Called!')
  })

  it('from clause works', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div2 = make('<div id="d2"></div>')
    var div1 = make('<div hx-trigger="click from:#d2" hx-get="/test">Requests: 0</div>')
    div1.innerHTML.should.equal('Requests: 0')
    div1.click()
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 0')
    div2.click()
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with body selector', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div1 = make('<div hx-trigger="click from:body" hx-get="/test">Requests: 0</div>')
    div1.innerHTML.should.equal('Requests: 0')
    document.body.click()
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with document selector', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div1 = make('<div hx-trigger="foo from:document" hx-get="/test">Requests: 0</div>')
    div1.innerHTML.should.equal('Requests: 0')
    htmx.trigger(document, 'foo')
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with window selector', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div1 = make('<div hx-trigger="foo from:window" hx-get="/test">Requests: 0</div>')
    div1.innerHTML.should.equal('Requests: 0')
    htmx.trigger(window, 'foo')
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with closest clause', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div1 = make('<div><a id="a1" hx-trigger="click from:closest div" hx-get="/test">Requests: 0</a></div>')
    var a1 = byId('a1')
    a1.innerHTML.should.equal('Requests: 0')
    div1.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with find clause', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div1 = make('<div hx-trigger="click from:find a" hx-target="#a1" hx-get="/test"><a id="a1">Requests: 0</a></div>')
    var a1 = byId('a1')
    a1.innerHTML.should.equal('Requests: 0')
    a1.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with next', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    make('<div hx-trigger="click from:next" hx-target="#a1" hx-get="/test"></div><a id="a1">Requests: 0</a>')
    var a1 = byId('a1')
    a1.innerHTML.should.equal('Requests: 0')
    a1.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 1')
  })

  it('from clause works with previous', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    make('<a id="a1">Requests: 0</a><div hx-trigger="click from:previous" hx-target="#a1" hx-get="/test"></div>')
    var a1 = byId('a1')
    a1.innerHTML.should.equal('Requests: 0')
    a1.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 1')
  })

  it('event listeners on other elements are removed when an element is swapped out', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/test2', 'Clicked')

    var div1 = make('<div hx-get="/test2">' +
            '<div id="d2" hx-trigger="click from:body" hx-get="/test">Requests: 0</div>' +
            '</div>')
    var div2 = byId('d2')

    div2.innerHTML.should.equal('Requests: 0')
    document.body.click()
    this.server.respond()
    requests.should.equal(1)

    requests.should.equal(1)

    div1.click()
    this.server.respond()
    div1.innerHTML.should.equal('Clicked')

    requests.should.equal(2)

    document.body.click()
    this.server.respond()

    requests.should.equal(2)
  })

  it('multiple triggers with from clauses mixed in work', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div2 = make('<div id="d2"></div>')
    var div1 = make('<div hx-trigger="click from:#d2, click" hx-get="/test">Requests: 0</div>')
    div1.innerHTML.should.equal('Requests: 0')
    div1.click()
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 1')
    div2.click()
    this.server.respond()
    div1.innerHTML.should.equal('Requests: 2')
  })

  it('from clause works with multiple extended selectors', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    make('<button id="btn" type="button">Click me</button>' +
      '<div hx-trigger="click from:(previous button, next a)" hx-target="#a1" hx-get="/test"></div>' +
      '<a id="a1">Requests: 0</a>')
    var btn = byId('btn')
    var a1 = byId('a1')
    a1.innerHTML.should.equal('Requests: 0')
    btn.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 1')
    a1.click()
    this.server.respond()
    a1.innerHTML.should.equal('Requests: 2')
  })

  it('event listeners can filter on target', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })

    var div1 = make('<div>' +
            '<div id="d1" hx-trigger="click from:body target:#d3" hx-get="/test">Requests: 0</div>' +
            '<div id="d2"></div>' +
            '<div id="d3"></div>' +
            '</div>')
    var div1 = byId('d1')
    var div2 = byId('d2')
    var div3 = byId('d3')

    div1.innerHTML.should.equal('Requests: 0')
    document.body.click()
    this.server.respond()
    requests.should.equal(0)

    div1.click()
    this.server.respond()
    requests.should.equal(0)

    div2.click()
    this.server.respond()
    requests.should.equal(0)

    div3.click()
    this.server.respond()
    requests.should.equal(1)
  })

  it('consume prevents event propagation', function() {
    this.server.respondWith('GET', '/foo', 'foo')
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click' hx-get='/foo'>" +
            "   <div id='d1' hx-trigger='click consume' hx-get='/bar'></div>" +
            '</div>')

    byId('d1').click()
    this.server.respond()

    // should not have been replaced by click
    byId('d1').parentElement.should.equal(div)
    byId('d1').innerText.should.equal('bar')
  })

  it('throttle prevents multiple requests from happening', function(done) {
    var requests = 0
    var server = this.server
    server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click throttle:10ms' hx-get='/test'></div>")

    div.click()
    server.respond()

    div.click()
    server.respond()

    div.click()
    server.respond()

    div.click()
    server.respond()

    // should not have been replaced by click
    div.innerText.should.equal('Requests: 1')

    setTimeout(function() {
      div.click()
      server.respond()
      div.innerText.should.equal('Requests: 2')

      div.click()
      server.respond()
      div.innerText.should.equal('Requests: 2')

      done()
    }, 50)
  })

  it('A throttle of 0 does not prevent multiple requests from happening', function(done) {
    var requests = 0
    var server = this.server
    server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    server.respondWith('GET', '/bar', 'bar')
    var div = make(
      "<div hx-trigger='click throttle:0ms' hx-get='/test'></div>"
    )

    div.click()
    server.respond()
    div.innerText.should.equal('Requests: 1')

    div.click()
    server.respond()
    div.innerText.should.equal('Requests: 2')

    div.click()
    server.respond()
    div.innerText.should.equal('Requests: 3')

    div.click()
    server.respond()
    div.innerText.should.equal('Requests: 4')

    done()
  })

  it('delay delays the request', function(done) {
    var requests = 0
    var server = this.server
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click delay:10ms' hx-get='/test'></div>")

    div.click()
    this.server.respond()

    div.click()
    this.server.respond()

    div.click()
    this.server.respond()

    div.click()
    this.server.respond()
    div.innerText.should.equal('')

    setTimeout(function() {
      server.respond()
      div.innerText.should.equal('Requests: 1')

      div.click()
      server.respond()
      div.innerText.should.equal('Requests: 1')

      done()
    }, 50)
  })

  it('A 0 delay does not delay the request', function(done) {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make(
      "<div hx-trigger='click delay:0ms' hx-get='/test'></div>"
    )

    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 2')

    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 3')

    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 4')

    done()
  })

  it('requests are queued with last one winning by default', function() {
    var requests = 0
    var server = this.server
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click' hx-get='/test'></div>")

    div.click()
    div.click()
    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    this.server.respond()
    div.innerText.should.equal('Requests: 2')

    this.server.respond()
    div.innerText.should.equal('Requests: 2')
  })

  it('queue:all queues all requests', function() {
    var requests = 0
    var server = this.server
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click queue:all' hx-get='/test'></div>")

    div.click()
    div.click()
    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    this.server.respond()
    div.innerText.should.equal('Requests: 2')

    this.server.respond()
    div.innerText.should.equal('Requests: 3')
  })

  it('queue:first queues first request', function() {
    var requests = 0
    var server = this.server
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click queue:first' hx-get='/test'></div>")

    div.click()
    div.click()
    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    this.server.respond()
    div.innerText.should.equal('Requests: 2')

    this.server.respond()
    div.innerText.should.equal('Requests: 2')
  })

  it('queue:none queues no requests', function() {
    var requests = 0
    var server = this.server
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    this.server.respondWith('GET', '/bar', 'bar')
    var div = make("<div hx-trigger='click queue:none' hx-get='/test'></div>")

    div.click()
    div.click()
    div.click()
    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    this.server.respond()
    div.innerText.should.equal('Requests: 1')

    this.server.respond()
    div.innerText.should.equal('Requests: 1')
  })

  it('load event works w/ positive filters', function() {
    this.server.respondWith('GET', '/test', 'Loaded!')
    var div = make('<div hx-get="/test" hx-trigger="load[true]">Load Me!</div>')
    div.innerHTML.should.equal('Load Me!')
    this.server.respond()
    div.innerHTML.should.equal('Loaded!')
  })

  it('load event works w/ negative filters', function() {
    this.server.respondWith('GET', '/test', 'Loaded!')
    var div = make('<div hx-get="/test" hx-trigger="load[false]">Load Me!</div>')
    div.innerHTML.should.equal('Load Me!')
    this.server.respond()
    div.innerHTML.should.equal('Load Me!')
  })

  it('reveal event works on two elements', function() {
    this.server.respondWith('GET', '/test1', 'test 1')
    this.server.respondWith('GET', '/test2', 'test 2')
    var div = make('<div hx-get="/test1" hx-trigger="revealed"></div>')
    var div2 = make('<div hx-get="/test2" hx-trigger="revealed"></div>')
    div.innerHTML.should.equal('')
    div2.innerHTML.should.equal('')
    htmx.trigger(div, 'revealed')
    htmx.trigger(div2, 'revealed')
    this.server.respondAll()
    div.innerHTML.should.equal('test 1')
    div2.innerHTML.should.equal('test 2')
  })

  it('scrolling triggers revealed event', function(done) {
    this.server.respondWith('GET', '/test', 'test')
    this.server.autoRespond = true
    this.server.autoRespondAfter = 0
    var div = make('<div hx-get="/test" hx-trigger="revealed"></div>')
    div.innerHTML.should.equal('')
    div.scrollIntoView({ block: 'end', behavior: htmx.config.scrollBehavior })
    htmx.trigger(document.body, 'scroll')

    setTimeout(function() {
      div.innerHTML.should.equal('test')
      done()
    }, 250)
  })

  if (/headlesschrome/i.test(navigator.userAgent)) {
    it('scrolling triggers intersect event', function(done) {
      // test only works reliably with playwright
      this.server.respondWith('GET', '/test', 'test')
      this.server.autoRespond = true
      this.server.autoRespondAfter = 0
      var div = make('<div hx-get="/test" hx-trigger="intersect"></div>')
      div.innerHTML.should.equal('')
      div.scrollIntoView({ block: 'end', behavior: htmx.config.scrollBehavior })
      htmx.trigger(document.body, 'scroll')

      setTimeout(function() {
        div.innerHTML.should.equal('test')
        done()
      }, 250)
    })
  }

  it('triggering revealed while component not yet inited still works', function(done) {
    this.server.respondWith('GET', '/test', 'test')
    var div = make('<div hx-get="/test" hx-trigger="revealed"></div>')
    var data = div['htmx-internal-data']
    delete data.initHash // simulate not inited or revealed yet
    div.removeAttribute('data-hx-revealed')
    var server1 = this.server
    div.innerHTML.should.equal('')
    div.scrollIntoView({ block: 'end', behavior: htmx.config.scrollBehavior })
    htmx.trigger(document.body, 'scroll')
    setTimeout(function() {
      server1.autoRespond = true
      server1.autoRespondAfter = 0
      htmx.process(div) // processing the div should also trigger revealed event now
      setTimeout(function() {
        div.innerHTML.should.equal('test')
        done()
      }, 10)
    }, 250)
  })

  it('reveal event works when triggered by window', function() {
    this.server.respondWith('GET', '/test1', 'test 1')
    var div = make('<div hx-get="/test1" hx-trigger="revealed" style="position: fixed; top: 1px; left: 1px; border: 3px solid red">foo</div>')
    div.innerHTML.should.equal('foo')
    this.server.respondAll()
    div.innerHTML.should.equal('test 1')
  })

  it('revealed can be paired w/ other events', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div = make('<div hx-get="/test" hx-trigger="revealed, click" style="position: fixed; top: 1px; left: 1px; border: 3px solid red">foo</div>')
    div.innerHTML.should.equal('foo')
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    div.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  it('revealed doesnt cause other events to trigger', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div = make('<div hx-get="/test" hx-trigger="revealedToTheWorld" style="position: fixed; top: 1px; left: 1px; border: 3px solid red">foo</div>')
    div.innerHTML.should.equal('foo')
    this.server.respondAll()
    div.innerHTML.should.equal('foo')
  })

  it('fires the htmx:trigger event when an AJAX attribute is specified', function() {
    var param = 'foo'
    var handler = htmx.on('htmx:trigger', function(evt) {
      param = 'bar'
    })
    try {
      this.server.respondWith('GET', '/test1', 'test 1')
      var div = make('<button hx-get="/test1">Submit</button>')
      div.click()
      should.equal(param, 'bar')
    } finally {
      htmx.off('htmx:trigger', handler)
    }
  })

  it('fires the htmx:trigger event when no AJAX attribute is specified', function() {
    var param = 'foo'
    var handler = htmx.on('htmx:trigger', function(evt) {
      param = 'bar'
    })
    try {
      var div = make('<button hx-trigger="click">Submit</button>')
      div.click()
      should.equal(param, 'bar')
    } finally {
      htmx.off('htmx:trigger', handler)
    }
  })

  it('fires the htmx:trigger event for delayed triggers', function(done) {
    var param = 'foo'
    var handler = htmx.on('htmx:trigger', function(evt) {
      param = 'bar'
    })
    var div = make('<button hx-trigger="click delay:10ms">Submit</button>')
    div.click()
    setTimeout(function() {
      try {
        should.equal(param, 'bar')
        done()
      } finally {
        htmx.off('htmx:trigger', handler)
      }
    }, 50)
  })

  it('fires the htmx:trigger event when the trigger is a load', function(done) {
    this.server.respondWith(
      'GET',
      '/test',
      '<div hx-trigger="load delay:50ms" hx-on::trigger="this.innerText = \'Done\'">Response</div>'
    )

    var div = make('<div hx-get="/test">Submit</div>')
    div.click()
    this.server.respond()
    var response = div.children[0]
    response.innerText.should.equal('Response')

    setTimeout(function() {
      try {
        response.innerText.should.equal('Done')
        done()
      } finally {
      }
    }, 100)
  })

  it('filters support "this" reference to the current element', function() {
    this.server.respondWith('GET', '/test', 'Called!')
    var form = make('<form hx-get="/test" hx-trigger="click[this.classList.contains(\'bar\')]">Not Called</form>')
    form.click()
    this.server.respond()
    form.innerHTML.should.equal('Not Called')

    form.classList.add('bar')
    form.click()
    this.server.respond()
    form.innerHTML.should.equal('Called!')
  })

  it('correctly handles CSS descendant combinators', function() {
    this.server.respondWith('GET', '/test', 'Clicked!')

    var outer = make(`
        <div>
            <div id='outer'>
                <div id='first'>
                    <div id='inner'></div>
                </div>
                <div id='second' hx-get='/test' hx-trigger='click from:previous (#outer div)'>Unclicked.</div>
            </div>
            <div id='other' hx-get='/test' hx-trigger='click from:(div #inner)'>Unclicked.</div>
        </div>
        `)

    var inner = byId('inner')
    var second = byId('second')
    var other = byId('other')

    second.innerHTML.should.equal('Unclicked.')
    other.innerHTML.should.equal('Unclicked.')

    inner.click()
    this.server.respond()

    second.innerHTML.should.equal('Clicked!')
    other.innerHTML.should.equal('Clicked!')
  })

  it('correctly handles CSS descendant combinators in modifier target', function() {
    this.server.respondWith('GET', '/test', 'Called')

    make('<div class="d1"><a id="a1" class="a1">Click me</a><a id="a2" class="a2">Click me</a></div>')
    var div = make('<div hx-trigger="click from:body target:(.d1 .a2)" hx-get="/test">Not Called</div>')

    byId('a1').click()
    this.server.respond()
    div.innerHTML.should.equal('Not Called')

    byId('a2').click()
    this.server.respond()
    div.innerHTML.should.equal('Called')
  })

  it('correctly handles CSS descendant combinators in modifier root', function() {
    this.server.respondWith('GET', '/test', 'Called')
    var errorEvent = null
    var handler = htmx.on('htmx:syntax:error', function(event) {
      errorEvent = event
    })
    var form = make('<div hx-trigger="intersect root:{form input}" hx-get="/test">Not Called</div>')
    try {
      var event = htmx._('makeEvent')('evt')
      form.dispatchEvent(event)
      should.equal(null, errorEvent)
    } finally {
      htmx.off('htmx:syntax:error', handler)
    }
  })

  it('correctly handles intersect with modifier threshold', function() {
    this.server.respondWith('GET', '/test', 'Called')
    var errorEvent = null
    var handler = htmx.on('htmx:syntax:error', function(event) {
      errorEvent = event
    })
    var form = make('<div hx-trigger="intersect threshold:0.5" hx-get="/test">Not Called</div>')
    try {
      var event = htmx._('makeEvent')('evt')
      form.dispatchEvent(event)
      should.equal(null, errorEvent)
    } finally {
      htmx.off('htmx:syntax:error', handler)
    }
  })

  it('issues error with invalid trigger spec', function() {
    this.server.respondWith('GET', '/test', 'Called')
    var errorEvent = null
    var handler = htmx.on('htmx:syntax:error', function(event) {
      errorEvent = event
    })
    var form = make('<div hx-trigger="intersect invalid:0.5" hx-get="/test">Not Called</div>')
    try {
      var event = htmx._('makeEvent')('evt')
      form.dispatchEvent(event)
      should.not.equal(null, errorEvent)
      should.not.equal(null, errorEvent.detail.source)
      console.log(errorEvent.detail.source)
    } finally {
      htmx.off('htmx:syntax:error', handler)
    }
  })

  it('uses trigger specs cache if defined', function() {
    var initialCacheConfig = htmx.config.triggerSpecsCache
    htmx.config.triggerSpecsCache = {}

    var specExamples = {
      'every 1s': [{ trigger: 'every', pollInterval: 1000 }],
      click: [{ trigger: 'click' }],
      customEvent: [{ trigger: 'customEvent' }],
      'event changed': [{ trigger: 'event', changed: true }],
      'event once': [{ trigger: 'event', once: true }],
      'event delay:1s': [{ trigger: 'event', delay: 1000 }],
      'event throttle:1s': [{ trigger: 'event', throttle: 1000 }],
      'event delay:1s, foo': [{ trigger: 'event', delay: 1000 }, { trigger: 'foo' }],
      'event throttle:1s, foo': [{ trigger: 'event', throttle: 1000 }, { trigger: 'foo' }],
      'event changed once delay:1s': [{ trigger: 'event', changed: true, once: true, delay: 1000 }],
      'event1,event2': [{ trigger: 'event1' }, { trigger: 'event2' }],
      'event1, event2': [{ trigger: 'event1' }, { trigger: 'event2' }],
      'event1 once, event2 changed': [{ trigger: 'event1', once: true }, { trigger: 'event2', changed: true }]
    }

    for (var specString in specExamples) {
      var div = make("<div hx-trigger='" + specString + "'></div>")
      var spec = htmx._('getTriggerSpecs')(div)
      spec.should.deep.equal(specExamples[specString], 'Found : ' + JSON.stringify(spec) + ', expected : ' + JSON.stringify(specExamples[specString]) + ' for spec: ' + specString)
    }

    Object.keys(htmx.config.triggerSpecsCache).length.should.greaterThan(0)
    Object.keys(htmx.config.triggerSpecsCache).length.should.equal(Object.keys(specExamples).length)

    htmx.config.triggerSpecsCache = initialCacheConfig
  })

  it('correctly reuses trigger specs from the cache if defined', function() {
    var initialCacheConfig = htmx.config.triggerSpecsCache
    htmx.config.triggerSpecsCache = {}

    var triggerStr = 'event changed once delay:1s'
    var expectedSpec = [{ trigger: 'event', changed: true, once: true, delay: 1000 }]

    var div = make("<div hx-trigger='event changed once delay:1s'></div>")
    var spec = htmx._('getTriggerSpecs')(div)
    spec.should.deep.equal(expectedSpec, 'Found : ' + JSON.stringify(spec) + ', expected : ' + JSON.stringify(expectedSpec) + ' for spec: ' + triggerStr)
    spec.push('This should be carried to further identical specs thanks to the cache')

    var div2 = make("<div hx-trigger='event changed once delay:1s'></div>")
    var spec2 = htmx._('getTriggerSpecs')(div2)
    spec2.should.deep.equal(spec, 'Found : ' + JSON.stringify(spec) + ', expected : ' + JSON.stringify(spec2) + ' for cached spec: ' + triggerStr)

    Object.keys(htmx.config.triggerSpecsCache).length.should.equal(1)

    htmx.config.triggerSpecsCache = initialCacheConfig
  })

  it('handles spaces at the end of trigger specs', function() {
    var requests = 0
    this.server.respondWith('GET', '/test', function(xhr) {
      requests++
      xhr.respond(200, {}, 'Requests: ' + requests)
    })
    var div = make('<div hx-trigger="load , click consume " hx-get="/test">Requests: 0</div>')
    div.innerHTML.should.equal('Requests: 0')
    this.server.respond()
    div.innerHTML.should.equal('Requests: 1')
    div.click()
    this.server.respond()
    div.innerHTML.should.equal('Requests: 2')
  })

  it('Removing polling trigger and processing node removes timeout', function(complete) {
    this.server.respondWith('GET', '/test', 'Called!')
    var div = make('<div hx-get="/test" hx-trigger="every 5ms">Not Called</div>')
    div.removeAttribute('hx-trigger')
    should.not.equal(div['htmx-internal-data'].timeout, undefined)
    htmx.process(div)
    should.equal(div['htmx-internal-data'].timeout, undefined)
    this.server.autoRespond = true
    this.server.autoRespondAfter = 0
    setTimeout(function() {
      div.innerHTML.should.equal('Not Called')
      delete window.foo
      complete()
    }, 30)
  })
})
