<commit_before>/* global HTMLDivElement */

const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise, timeoutPromise} = require('./async-spec-helpers')
const TextEditor = require('../src/text-editor')
const TextEditorElement = require('../src/text-editor-element')

describe('TextEditorElement', () => {
  let jasmineContent

  beforeEach(() => {
    jasmineContent = document.body.querySelector('#jasmine-content')
  })

  function buildTextEditorElement (options = {}) {
    const element = new TextEditorElement()
    element.setUpdatedSynchronously(false)
    if (options.attach !== false) jasmine.attachToDOM(element)
    return element
  }

  it("honors the 'mini' attribute", () => {
    jasmineContent.innerHTML = '<atom-text-editor mini>'
    const element = jasmineContent.firstChild
    expect(element.getModel().isMini()).toBe(true)
  })

  it("honors the 'placeholder-text' attribute", () => {
    jasmineContent.innerHTML = "<atom-text-editor placeholder-text='testing'>"
    const element = jasmineContent.firstChild
    expect(element.getModel().getPlaceholderText()).toBe('testing')
  })

  it("only assigns 'placeholder-text' on the model if the attribute is present", () => {
    const editor = new TextEditor({placeholderText: 'placeholder'})
    editor.getElement()
    expect(editor.getPlaceholderText()).toBe('placeholder')
  })

  it("honors the 'gutter-hidden' attribute", () => {
    jasmineContent.innerHTML = '<atom-text-editor gutter-hidden>'
    const element = jasmineContent.firstChild
    expect(element.getModel().isLineNumberGutterVisible()).toBe(false)

    element.removeAttribute('gutter-hidden')
    expect(element.getModel().isLineNumberGutterVisible()).toBe(true)

    element.setAttribute('gutter-hidden', '')
    expect(element.getModel().isLineNumberGutterVisible()).toBe(false)
  })

  it('honors the text content', () => {
    jasmineContent.innerHTML = '<atom-text-editor>testing</atom-text-editor>'
    const element = jasmineContent.firstChild
    expect(element.getModel().getText()).toBe('testing')
  })

  describe('when the model is assigned', () =>
    it("adds the 'mini' attribute if .isMini() returns true on the model", function (done) {
      const element = buildTextEditorElement()
      element.getModel().update({mini: true})
      atom.views.getNextUpdatePromise().then(() => {
        expect(element.hasAttribute('mini')).toBe(true)
        done()
      })
    })
  )

  describe('when the editor is attached to the DOM', () =>
    it('mounts the component and unmounts when removed from the dom', () => {
      const element = buildTextEditorElement()

      const { component } = element
      expect(component.attached).toBe(true)
      element.remove()
      expect(component.attached).toBe(false)

      jasmine.attachToDOM(element)
      expect(element.component.attached).toBe(true)
    })
  )

  describe('when the editor is detached from the DOM and then reattached', () => {
    it('does not render duplicate line numbers', () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3')
      const element = editor.getElement()
      jasmine.attachToDOM(element)

      const initialCount = element.querySelectorAll('.line-number').length

      element.remove()
      jasmine.attachToDOM(element)
      expect(element.querySelectorAll('.line-number').length).toBe(initialCount)
    })

    it('does not render duplicate decorations in custom gutters', () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3')
      editor.addGutter({name: 'test-gutter'})
      const marker = editor.markBufferRange([[0, 0], [2, 0]])
      editor.decorateMarker(marker, {type: 'gutter', gutterName: 'test-gutter'})
      const element = editor.getElement()

      jasmine.attachToDOM(element)
      const initialDecorationCount = element.querySelectorAll('.decoration').length

      element.remove()
      jasmine.attachToDOM(element)
      expect(element.querySelectorAll('.decoration').length).toBe(initialDecorationCount)
    })

    it('can be re-focused using the previous `document.activeElement`', () => {
      const editorElement = buildTextEditorElement()
      editorElement.focus()

      const { activeElement } = document

      editorElement.remove()
      jasmine.attachToDOM(editorElement)
      activeElement.focus()

      expect(editorElement.hasFocus()).toBe(true)
    })
  })

  describe('focus and blur handling', () => {
    it('proxies focus/blur events to/from the hidden input', () => {
      const element = buildTextEditorElement()
      jasmineContent.appendChild(element)

      let blurCalled = false
      element.addEventListener('blur', () => {
        blurCalled = true
      })

      element.focus()
      expect(blurCalled).toBe(false)
      expect(element.hasFocus()).toBe(true)
      expect(document.activeElement).toBe(element.querySelector('input'))

      document.body.focus()
      expect(blurCalled).toBe(true)
    })

    it("doesn't trigger a blur event on the editor element when focusing an already focused editor element", () => {
      let blurCalled = false
      const element = buildTextEditorElement()
      element.addEventListener('blur', () => { blurCalled = true })

      jasmineContent.appendChild(element)
      expect(document.activeElement).toBe(document.body)
      expect(blurCalled).toBe(false)

      element.focus()
      expect(document.activeElement).toBe(element.querySelector('input'))
      expect(blurCalled).toBe(false)

      element.focus()
      expect(document.activeElement).toBe(element.querySelector('input'))
      expect(blurCalled).toBe(false)
    })

    describe('when focused while a parent node is being attached to the DOM', () => {
      class ElementThatFocusesChild extends HTMLDivElement {
        attachedCallback () {
          this.firstChild.focus()
        }
      }

      document.registerElement('element-that-focuses-child',
        {prototype: ElementThatFocusesChild.prototype}
      )

      it('proxies the focus event to the hidden input', () => {
        const element = buildTextEditorElement()
        const parentElement = document.createElement('element-that-focuses-child')
        parentElement.appendChild(element)
        jasmineContent.appendChild(parentElement)
        expect(document.activeElement).toBe(element.querySelector('input'))
      })
    })
  })

  describe('::onDidAttach and ::onDidDetach', () =>
    it('invokes callbacks when the element is attached and detached', () => {
      const element = buildTextEditorElement({attach: false})

      const attachedCallback = jasmine.createSpy('attachedCallback')
      const detachedCallback = jasmine.createSpy('detachedCallback')

      element.onDidAttach(attachedCallback)
      element.onDidDetach(detachedCallback)

      jasmine.attachToDOM(element)
      expect(attachedCallback).toHaveBeenCalled()
      expect(detachedCallback).not.toHaveBeenCalled()

      attachedCallback.reset()
      element.remove()

      expect(attachedCallback).not.toHaveBeenCalled()
      expect(detachedCallback).toHaveBeenCalled()
    })
  )

  describe('::setUpdatedSynchronously', () =>
    it('controls whether the text editor is updated synchronously', () => {
      spyOn(window, 'requestAnimationFrame').andCallFake(fn => fn())

      const element = buildTextEditorElement()
      jasmine.attachToDOM(element)

      expect(element.isUpdatedSynchronously()).toBe(false)

      element.getModel().setText('hello')
      expect(window.requestAnimationFrame).toHaveBeenCalled()

      expect(element.textContent).toContain('hello')

      window.requestAnimationFrame.reset()
      element.setUpdatedSynchronously(true)
      element.getModel().setText('goodbye')
      expect(window.requestAnimationFrame).not.toHaveBeenCalled()
      expect(element.textContent).toContain('goodbye')
    })
  )

  describe('::getDefaultCharacterWidth', () => {
    it('returns null before the element is attached', () => {
      const element = buildTextEditorElement({attach: false})
      expect(element.getDefaultCharacterWidth()).toBeNull()
    })

    it('returns the width of a character in the root scope', () => {
      const element = buildTextEditorElement()
      jasmine.attachToDOM(element)
      expect(element.getDefaultCharacterWidth()).toBeGreaterThan(0)
    })
  })

  describe('::getMaxScrollTop', () =>
    it('returns the maximum scroll top that can be applied to the element', async () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16')
      const element = editor.getElement()
      element.style.lineHeight = '10px'
      element.style.width = '200px'
      jasmine.attachToDOM(element)

      expect(element.getMaxScrollTop()).toBe(0)
      await editor.update({autoHeight: false})

      element.style.height = '100px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(60)

      element.style.height = '120px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(40)

      element.style.height = '200px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(0)
    })
  )

  describe('::setScrollTop and ::setScrollLeft', () => {
    it('changes the scroll position', async () => {
      element = buildTextEditorElement()
      element.getModel().update({autoHeight: false})
      element.getModel().setText('lorem\nipsum\ndolor\nsit\namet')
      element.setHeight(20)
      await element.getNextUpdatePromise()
      element.setWidth(20)
      await element.getNextUpdatePromise()

      element.setScrollTop(22)
      await element.getNextUpdatePromise()
      expect(element.getScrollTop()).toBe(22)

      element.setScrollLeft(32)
      await element.getNextUpdatePromise()
      expect(element.getScrollLeft()).toBe(32)
    })
  })

  describe('on TextEditor::setMini', () =>
    it("changes the element's 'mini' attribute", async () => {
      const element = buildTextEditorElement()
      expect(element.hasAttribute('mini')).toBe(false)
      element.getModel().setMini(true)
      await element.getNextUpdatePromise()
      expect(element.hasAttribute('mini')).toBe(true)
      element.getModel().setMini(false)
      await element.getNextUpdatePromise()
      expect(element.hasAttribute('mini')).toBe(false)
    })
  )

  describe('events', () => {
    let element = null

    beforeEach(async () => {
      element = buildTextEditorElement()
      element.getModel().update({autoHeight: false})
      element.getModel().setText('lorem\nipsum\ndolor\nsit\namet')
      element.setHeight(20)
      await element.getNextUpdatePromise()
      element.setWidth(20)
      await element.getNextUpdatePromise()
    })

    describe('::onDidChangeScrollTop(callback)', () =>
      it('triggers even when subscribing before attaching the element', () => {
        const positions = []
        const subscription1 = element.onDidChangeScrollTop(p => positions.push(p))
        element.onDidChangeScrollTop(p => positions.push(p))

        positions.length = 0
        element.setScrollTop(10)
        expect(positions).toEqual([10, 10])

        element.remove()
        jasmine.attachToDOM(element)

        positions.length = 0
        element.setScrollTop(20)
        expect(positions).toEqual([20, 20])

        subscription1.dispose()

        positions.length = 0
        element.setScrollTop(30)
        expect(positions).toEqual([30])
      })
    )

    describe('::onDidChangeScrollLeft(callback)', () =>
      it('triggers even when subscribing before attaching the element', () => {
        const positions = []
        const subscription1 = element.onDidChangeScrollLeft(p => positions.push(p))
        element.onDidChangeScrollLeft(p => positions.push(p))

        positions.length = 0
        element.setScrollLeft(10)
        expect(positions).toEqual([10, 10])

        element.remove()
        jasmine.attachToDOM(element)

        positions.length = 0
        element.setScrollLeft(20)
        expect(positions).toEqual([20, 20])

        subscription1.dispose()

        positions.length = 0
        element.setScrollLeft(30)
        expect(positions).toEqual([30])
      })
    )
  })
})
<commit_msg>Add better test coverage for the mini and placeholder-text attributes<commit_after>/* global HTMLDivElement */

const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise, timeoutPromise} = require('./async-spec-helpers')
const TextEditor = require('../src/text-editor')
const TextEditorElement = require('../src/text-editor-element')

describe('TextEditorElement', () => {
  let jasmineContent

  beforeEach(() => {
    jasmineContent = document.body.querySelector('#jasmine-content')
  })

  function buildTextEditorElement (options = {}) {
    const element = new TextEditorElement()
    element.setUpdatedSynchronously(false)
    if (options.attach !== false) jasmine.attachToDOM(element)
    return element
  }

  it("honors the 'mini' attribute", () => {
    jasmineContent.innerHTML = '<atom-text-editor mini>'
    const element = jasmineContent.firstChild
    expect(element.getModel().isMini()).toBe(true)

    element.removeAttribute('mini')
    expect(element.getModel().isMini()).toBe(false)

    element.setAttribute('mini', '')
    expect(element.getModel().isMini()).toBe(true)
  })

  it("honors the 'placeholder-text' attribute", () => {
    jasmineContent.innerHTML = "<atom-text-editor placeholder-text='testing'>"
    const element = jasmineContent.firstChild
    expect(element.getModel().getPlaceholderText()).toBe('testing')

    element.setAttribute('placeholder-text', 'placeholder')
    expect(element.getModel().getPlaceholderText()).toBe('placeholder')

    element.removeAttribute('placeholder-text')
    expect(element.getModel().getPlaceholderText()).toBeNull()
  })

  it("only assigns 'placeholder-text' on the model if the attribute is present", () => {
    const editor = new TextEditor({placeholderText: 'placeholder'})
    editor.getElement()
    expect(editor.getPlaceholderText()).toBe('placeholder')
  })

  it("honors the 'gutter-hidden' attribute", () => {
    jasmineContent.innerHTML = '<atom-text-editor gutter-hidden>'
    const element = jasmineContent.firstChild
    expect(element.getModel().isLineNumberGutterVisible()).toBe(false)

    element.removeAttribute('gutter-hidden')
    expect(element.getModel().isLineNumberGutterVisible()).toBe(true)

    element.setAttribute('gutter-hidden', '')
    expect(element.getModel().isLineNumberGutterVisible()).toBe(false)
  })

  it('honors the text content', () => {
    jasmineContent.innerHTML = '<atom-text-editor>testing</atom-text-editor>'
    const element = jasmineContent.firstChild
    expect(element.getModel().getText()).toBe('testing')
  })

  describe('when the model is assigned', () =>
    it("adds the 'mini' attribute if .isMini() returns true on the model", function (done) {
      const element = buildTextEditorElement()
      element.getModel().update({mini: true})
      atom.views.getNextUpdatePromise().then(() => {
        expect(element.hasAttribute('mini')).toBe(true)
        done()
      })
    })
  )

  describe('when the editor is attached to the DOM', () =>
    it('mounts the component and unmounts when removed from the dom', () => {
      const element = buildTextEditorElement()

      const { component } = element
      expect(component.attached).toBe(true)
      element.remove()
      expect(component.attached).toBe(false)

      jasmine.attachToDOM(element)
      expect(element.component.attached).toBe(true)
    })
  )

  describe('when the editor is detached from the DOM and then reattached', () => {
    it('does not render duplicate line numbers', () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3')
      const element = editor.getElement()
      jasmine.attachToDOM(element)

      const initialCount = element.querySelectorAll('.line-number').length

      element.remove()
      jasmine.attachToDOM(element)
      expect(element.querySelectorAll('.line-number').length).toBe(initialCount)
    })

    it('does not render duplicate decorations in custom gutters', () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3')
      editor.addGutter({name: 'test-gutter'})
      const marker = editor.markBufferRange([[0, 0], [2, 0]])
      editor.decorateMarker(marker, {type: 'gutter', gutterName: 'test-gutter'})
      const element = editor.getElement()

      jasmine.attachToDOM(element)
      const initialDecorationCount = element.querySelectorAll('.decoration').length

      element.remove()
      jasmine.attachToDOM(element)
      expect(element.querySelectorAll('.decoration').length).toBe(initialDecorationCount)
    })

    it('can be re-focused using the previous `document.activeElement`', () => {
      const editorElement = buildTextEditorElement()
      editorElement.focus()

      const { activeElement } = document

      editorElement.remove()
      jasmine.attachToDOM(editorElement)
      activeElement.focus()

      expect(editorElement.hasFocus()).toBe(true)
    })
  })

  describe('focus and blur handling', () => {
    it('proxies focus/blur events to/from the hidden input', () => {
      const element = buildTextEditorElement()
      jasmineContent.appendChild(element)

      let blurCalled = false
      element.addEventListener('blur', () => {
        blurCalled = true
      })

      element.focus()
      expect(blurCalled).toBe(false)
      expect(element.hasFocus()).toBe(true)
      expect(document.activeElement).toBe(element.querySelector('input'))

      document.body.focus()
      expect(blurCalled).toBe(true)
    })

    it("doesn't trigger a blur event on the editor element when focusing an already focused editor element", () => {
      let blurCalled = false
      const element = buildTextEditorElement()
      element.addEventListener('blur', () => { blurCalled = true })

      jasmineContent.appendChild(element)
      expect(document.activeElement).toBe(document.body)
      expect(blurCalled).toBe(false)

      element.focus()
      expect(document.activeElement).toBe(element.querySelector('input'))
      expect(blurCalled).toBe(false)

      element.focus()
      expect(document.activeElement).toBe(element.querySelector('input'))
      expect(blurCalled).toBe(false)
    })

    describe('when focused while a parent node is being attached to the DOM', () => {
      class ElementThatFocusesChild extends HTMLDivElement {
        attachedCallback () {
          this.firstChild.focus()
        }
      }

      document.registerElement('element-that-focuses-child',
        {prototype: ElementThatFocusesChild.prototype}
      )

      it('proxies the focus event to the hidden input', () => {
        const element = buildTextEditorElement()
        const parentElement = document.createElement('element-that-focuses-child')
        parentElement.appendChild(element)
        jasmineContent.appendChild(parentElement)
        expect(document.activeElement).toBe(element.querySelector('input'))
      })
    })
  })

  describe('::onDidAttach and ::onDidDetach', () =>
    it('invokes callbacks when the element is attached and detached', () => {
      const element = buildTextEditorElement({attach: false})

      const attachedCallback = jasmine.createSpy('attachedCallback')
      const detachedCallback = jasmine.createSpy('detachedCallback')

      element.onDidAttach(attachedCallback)
      element.onDidDetach(detachedCallback)

      jasmine.attachToDOM(element)
      expect(attachedCallback).toHaveBeenCalled()
      expect(detachedCallback).not.toHaveBeenCalled()

      attachedCallback.reset()
      element.remove()

      expect(attachedCallback).not.toHaveBeenCalled()
      expect(detachedCallback).toHaveBeenCalled()
    })
  )

  describe('::setUpdatedSynchronously', () =>
    it('controls whether the text editor is updated synchronously', () => {
      spyOn(window, 'requestAnimationFrame').andCallFake(fn => fn())

      const element = buildTextEditorElement()
      jasmine.attachToDOM(element)

      expect(element.isUpdatedSynchronously()).toBe(false)

      element.getModel().setText('hello')
      expect(window.requestAnimationFrame).toHaveBeenCalled()

      expect(element.textContent).toContain('hello')

      window.requestAnimationFrame.reset()
      element.setUpdatedSynchronously(true)
      element.getModel().setText('goodbye')
      expect(window.requestAnimationFrame).not.toHaveBeenCalled()
      expect(element.textContent).toContain('goodbye')
    })
  )

  describe('::getDefaultCharacterWidth', () => {
    it('returns null before the element is attached', () => {
      const element = buildTextEditorElement({attach: false})
      expect(element.getDefaultCharacterWidth()).toBeNull()
    })

    it('returns the width of a character in the root scope', () => {
      const element = buildTextEditorElement()
      jasmine.attachToDOM(element)
      expect(element.getDefaultCharacterWidth()).toBeGreaterThan(0)
    })
  })

  describe('::getMaxScrollTop', () =>
    it('returns the maximum scroll top that can be applied to the element', async () => {
      const editor = new TextEditor()
      editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16')
      const element = editor.getElement()
      element.style.lineHeight = '10px'
      element.style.width = '200px'
      jasmine.attachToDOM(element)

      expect(element.getMaxScrollTop()).toBe(0)
      await editor.update({autoHeight: false})

      element.style.height = '100px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(60)

      element.style.height = '120px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(40)

      element.style.height = '200px'
      await element.getNextUpdatePromise()
      expect(element.getMaxScrollTop()).toBe(0)
    })
  )

  describe('::setScrollTop and ::setScrollLeft', () => {
    it('changes the scroll position', async () => {
      element = buildTextEditorElement()
      element.getModel().update({autoHeight: false})
      element.getModel().setText('lorem\nipsum\ndolor\nsit\namet')
      element.setHeight(20)
      await element.getNextUpdatePromise()
      element.setWidth(20)
      await element.getNextUpdatePromise()

      element.setScrollTop(22)
      await element.getNextUpdatePromise()
      expect(element.getScrollTop()).toBe(22)

      element.setScrollLeft(32)
      await element.getNextUpdatePromise()
      expect(element.getScrollLeft()).toBe(32)
    })
  })

  describe('on TextEditor::setMini', () =>
    it("changes the element's 'mini' attribute", async () => {
      const element = buildTextEditorElement()
      expect(element.hasAttribute('mini')).toBe(false)
      element.getModel().setMini(true)
      await element.getNextUpdatePromise()
      expect(element.hasAttribute('mini')).toBe(true)
      element.getModel().setMini(false)
      await element.getNextUpdatePromise()
      expect(element.hasAttribute('mini')).toBe(false)
    })
  )

  describe('events', () => {
    let element = null

    beforeEach(async () => {
      element = buildTextEditorElement()
      element.getModel().update({autoHeight: false})
      element.getModel().setText('lorem\nipsum\ndolor\nsit\namet')
      element.setHeight(20)
      await element.getNextUpdatePromise()
      element.setWidth(20)
      await element.getNextUpdatePromise()
    })

    describe('::onDidChangeScrollTop(callback)', () =>
      it('triggers even when subscribing before attaching the element', () => {
        const positions = []
        const subscription1 = element.onDidChangeScrollTop(p => positions.push(p))
        element.onDidChangeScrollTop(p => positions.push(p))

        positions.length = 0
        element.setScrollTop(10)
        expect(positions).toEqual([10, 10])

        element.remove()
        jasmine.attachToDOM(element)

        positions.length = 0
        element.setScrollTop(20)
        expect(positions).toEqual([20, 20])

        subscription1.dispose()

        positions.length = 0
        element.setScrollTop(30)
        expect(positions).toEqual([30])
      })
    )

    describe('::onDidChangeScrollLeft(callback)', () =>
      it('triggers even when subscribing before attaching the element', () => {
        const positions = []
        const subscription1 = element.onDidChangeScrollLeft(p => positions.push(p))
        element.onDidChangeScrollLeft(p => positions.push(p))

        positions.length = 0
        element.setScrollLeft(10)
        expect(positions).toEqual([10, 10])

        element.remove()
        jasmine.attachToDOM(element)

        positions.length = 0
        element.setScrollLeft(20)
        expect(positions).toEqual([20, 20])

        subscription1.dispose()

        positions.length = 0
        element.setScrollLeft(30)
        expect(positions).toEqual([30])
      })
    )
  })
})

{'fraction_non_alphanumeric': 0.1332084047685499, 'fraction_numerical': 0.008931063354730673, 'mean_word_length': 3.4653729748976323, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5303054', 'n_tokens_mistral': 7209, 'n_tokens_neox': 6928, 'n_words': 1228}