ContextMenuManager = require '../src/context-menu-manager'

describe "ContextMenuManager", ->
  [contextMenu, parent, child, grandchild] = []

  beforeEach ->
    {resourcePath} = atom.getLoadSettings()
    contextMenu = new ContextMenuManager({resourcePath, keymapManager: atom.keymaps})

    parent = document.createElement("div")
    child = document.createElement("div")
    grandchild = document.createElement("div")
    parent.classList.add('parent')
    child.classList.add('child')
    grandchild.classList.add('grandchild')
    child.appendChild(grandchild)
    parent.appendChild(child)

  describe "::add(itemsBySelector)", ->
    it "can add top-level menu items that can be removed with the returned disposable", ->
      disposable = contextMenu.add
        '.parent': [{label: 'A', command: 'a'}]
        '.child': [{label: 'B', command: 'b'}]
        '.grandchild': [{label: 'C', command: 'c'}]

      expect(contextMenu.templateForElement(grandchild)).toEqual [
        {label: 'C', command: 'c'}
        {label: 'B', command: 'b'}
        {label: 'A', command: 'a'}
      ]

      disposable.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual []

    it "can add submenu items to existing menus that can be removed with the returned disposable", ->
      disposable1 = contextMenu.add
        '.grandchild': [{label: 'A', submenu: [{label: 'B', command: 'b'}]}]
      disposable2 = contextMenu.add
        '.grandchild': [{label: 'A', submenu: [{label: 'C', command: 'c'}]}]

      expect(contextMenu.templateForElement(grandchild)).toEqual [{
        label: 'A',
        submenu: [
          {label: 'B', command: 'b'}
          {label: 'C', command: 'c'}
        ]
      }]

      disposable2.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual [{
        label: 'A',
        submenu: [
          {label: 'B', command: 'b'}
        ]
      }]

      disposable1.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual []

    it "favors the most specific / recently added item in the case of a duplicate label", ->
      grandchild.classList.add('foo')

      disposable1 = contextMenu.add
        '.grandchild': [{label: 'A', command: 'a'}]
      disposable2 = contextMenu.add
        '.grandchild.foo': [{label: 'A', command: 'b'}]
      disposable3 = contextMenu.add
        '.grandchild': [{label: 'A', command: 'c'}]
      disposable4 = contextMenu.add
        '.child': [{label: 'A', command: 'd'}]

      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'b'}]

      disposable2.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'c'}]

      disposable3.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}]

      disposable1.dispose()
      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'd'}]

    it "allows multiple separators, but not adjacent to each other", ->
      contextMenu.add
        '.grandchild': [
          {label: 'A', command: 'a'},
          {type: 'separator'},
          {type: 'separator'},
          {label: 'B', command: 'b'},
          {type: 'separator'},
          {type: 'separator'},
          {label: 'C', command: 'c'}
        ]

      expect(contextMenu.templateForElement(grandchild)).toEqual [
        {label: 'A', command: 'a'},
        {type: 'separator'},
        {label: 'B', command: 'b'},
        {type: 'separator'},
        {label: 'C', command: 'c'}
      ]

    it "excludes items marked for display in devMode unless in dev mode", ->
      disposable1 = contextMenu.add
        '.grandchild': [{label: 'A', command: 'a', devMode: true}, {label: 'B', command: 'b', devMode: false}]

      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'B', command: 'b'}]

      contextMenu.devMode = true
      expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}, {label: 'B', command: 'b'}]

    it "allows items to be associated with `created` hooks which are invoked on template construction with the item and event", ->
      createdEvent = null

      item = {
        label: 'A',
        command: 'a',
        created: (event) ->
          @command = 'b'
          createdEvent = event
      }

      contextMenu.add('.grandchild': [item])

      dispatchedEvent = {target: grandchild}
      expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual [{label: 'A', command: 'b'}]
      expect(item.command).toBe 'a' # doesn't modify original item template
      expect(createdEvent).toBe dispatchedEvent

    it "allows items to be associated with `shouldDisplay` hooks which are invoked on construction to determine whether the item should be included", ->
      shouldDisplayEvent = null
      shouldDisplay = true

      item = {
        label: 'A',
        command: 'a',
        shouldDisplay: (event) ->
          @foo = 'bar'
          shouldDisplayEvent = event
          shouldDisplay
      }
      contextMenu.add('.grandchild': [item])

      dispatchedEvent = {target: grandchild}
      expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual [{label: 'A', command: 'a'}]
      expect(item.foo).toBeUndefined() # doesn't modify original item template
      expect(shouldDisplayEvent).toBe dispatchedEvent

      shouldDisplay = false
      expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual []

    it "throws an error when the selector is invalid", ->
      addError = null
      try
        contextMenu.add '<>': [{label: 'A', command: 'a'}]
      catch error
        addError = error
      expect(addError.message).toContain('<>')
