mirror of
https://github.com/atom/atom.git
synced 2026-01-15 01:48:15 -05:00
Get TextEditorElement tests passing
This commit is contained in:
committed by
Antonio Scandurra
parent
eb33b5c39b
commit
c8f2fbb657
@@ -27,7 +27,7 @@
|
||||
"color": "^0.7.3",
|
||||
"dedent": "^0.6.0",
|
||||
"devtron": "1.3.0",
|
||||
"etch": "^0.12.0",
|
||||
"etch": "^0.12.2",
|
||||
"event-kit": "^2.3.0",
|
||||
"find-parent-dir": "^0.3.0",
|
||||
"first-mate": "7.0.4",
|
||||
|
||||
@@ -30,11 +30,12 @@ describe "TextEditorElement", ->
|
||||
expect(element.getModel().getText()).toBe 'testing'
|
||||
|
||||
describe "when the model is assigned", ->
|
||||
it "adds the 'mini' attribute if .isMini() returns true on the model", ->
|
||||
it "adds the 'mini' attribute if .isMini() returns true on the model", (done) ->
|
||||
element = new TextEditorElement
|
||||
model = new TextEditor({mini: true})
|
||||
element.setModel(model)
|
||||
expect(element.hasAttribute('mini')).toBe true
|
||||
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", ->
|
||||
@@ -42,12 +43,12 @@ describe "TextEditorElement", ->
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
component = element.component
|
||||
expect(component.mounted).toBe true
|
||||
expect(component.attached).toBe true
|
||||
element.remove()
|
||||
expect(component.mounted).toBe false
|
||||
expect(component.attached).toBe false
|
||||
|
||||
jasmine.attachToDOM(element)
|
||||
expect(element.component.mounted).toBe true
|
||||
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", ->
|
||||
@@ -140,40 +141,6 @@ describe "TextEditorElement", ->
|
||||
jasmineContent.appendChild(parentElement)
|
||||
expect(document.activeElement).toBe element.querySelector('input')
|
||||
|
||||
describe "when the themes finish loading", ->
|
||||
[themeReloadCallback, initialThemeLoadComplete, element] = []
|
||||
|
||||
beforeEach ->
|
||||
themeReloadCallback = null
|
||||
initialThemeLoadComplete = false
|
||||
|
||||
spyOn(atom.themes, 'isInitialLoadComplete').andCallFake ->
|
||||
initialThemeLoadComplete
|
||||
spyOn(atom.themes, 'onDidChangeActiveThemes').andCallFake (fn) ->
|
||||
themeReloadCallback = fn
|
||||
new Disposable
|
||||
|
||||
element = new TextEditorElement()
|
||||
element.style.height = '200px'
|
||||
element.getModel().update({autoHeight: false})
|
||||
element.getModel().setText [0..20].join("\n")
|
||||
|
||||
it "re-renders the scrollbar", ->
|
||||
jasmineContent.appendChild(element)
|
||||
|
||||
atom.styles.addStyleSheet("""
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
""", context: 'atom-text-editor')
|
||||
|
||||
initialThemeLoadComplete = true
|
||||
themeReloadCallback()
|
||||
|
||||
verticalScrollbarNode = element.querySelector(".vertical-scrollbar")
|
||||
scrollbarWidth = verticalScrollbarNode.offsetWidth - verticalScrollbarNode.clientWidth
|
||||
expect(scrollbarWidth).toEqual(8)
|
||||
|
||||
describe "::onDidAttach and ::onDidDetach", ->
|
||||
it "invokes callbacks when the element is attached and detached", ->
|
||||
element = new TextEditorElement
|
||||
@@ -236,19 +203,13 @@ describe "TextEditorElement", ->
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
expect(element.getMaxScrollTop()).toBe(0)
|
||||
|
||||
element.style.height = '100px'
|
||||
editor.update({autoHeight: false})
|
||||
element.component.measureDimensions()
|
||||
expect(element.getMaxScrollTop()).toBe(60)
|
||||
|
||||
element.style.height = '120px'
|
||||
element.component.measureDimensions()
|
||||
expect(element.getMaxScrollTop()).toBe(40)
|
||||
|
||||
element.style.height = '200px'
|
||||
element.component.measureDimensions()
|
||||
expect(element.getMaxScrollTop()).toBe(0)
|
||||
waitsForPromise -> editor.update({autoHeight: false})
|
||||
runs -> element.style.height = '100px'
|
||||
waitsFor -> element.getMaxScrollTop() is 60
|
||||
runs -> element.style.height = '120px'
|
||||
waitsFor -> element.getMaxScrollTop() is 40
|
||||
runs -> element.style.height = '200px'
|
||||
waitsFor -> element.getMaxScrollTop() is 0
|
||||
|
||||
describe "on TextEditor::setMini", ->
|
||||
it "changes the element's 'mini' attribute", ->
|
||||
@@ -256,9 +217,9 @@ describe "TextEditorElement", ->
|
||||
jasmine.attachToDOM(element)
|
||||
expect(element.hasAttribute('mini')).toBe false
|
||||
element.getModel().setMini(true)
|
||||
expect(element.hasAttribute('mini')).toBe true
|
||||
element.getModel().setMini(false)
|
||||
expect(element.hasAttribute('mini')).toBe false
|
||||
waitsFor -> element.hasAttribute('mini')
|
||||
runs -> element.getModel().setMini(false)
|
||||
waitsFor -> not element.hasAttribute('mini')
|
||||
|
||||
describe "events", ->
|
||||
element = null
|
||||
|
||||
@@ -74,6 +74,7 @@ class TextEditorComponent {
|
||||
this.nextUpdateOnlyBlinksCursors = null
|
||||
this.horizontalPositionsToMeasure = new Map() // Keys are rows with positions we want to measure, values are arrays of columns to measure
|
||||
this.horizontalPixelPositionsByScreenLineId = new Map() // Values are maps from column to horiontal pixel positions
|
||||
this.blockDecorationsToMeasure = new Set()
|
||||
this.lineNodesByScreenLineId = new Map()
|
||||
this.textNodesByScreenLineId = new Map()
|
||||
this.shouldRenderDummyScrollbars = true
|
||||
@@ -114,27 +115,6 @@ class TextEditorComponent {
|
||||
this.gutterContainerVnode = null
|
||||
this.cursorsVnode = null
|
||||
this.placeholderTextVnode = null
|
||||
this.blockDecorationMeasurementAreaVnode = $.div({
|
||||
ref: 'blockDecorationMeasurementArea',
|
||||
key: 'blockDecorationMeasurementArea',
|
||||
style: {
|
||||
contain: 'strict',
|
||||
position: 'absolute',
|
||||
visibility: 'hidden'
|
||||
}
|
||||
})
|
||||
this.characterMeasurementLineVnode = $.div(
|
||||
{
|
||||
key: 'characterMeasurementLine',
|
||||
ref: 'characterMeasurementLine',
|
||||
className: 'line dummy',
|
||||
style: {position: 'absolute', visibility: 'hidden'}
|
||||
},
|
||||
$.span({ref: 'normalWidthCharacterSpan'}, NORMAL_WIDTH_CHARACTER),
|
||||
$.span({ref: 'doubleWidthCharacterSpan'}, DOUBLE_WIDTH_CHARACTER),
|
||||
$.span({ref: 'halfWidthCharacterSpan'}, HALF_WIDTH_CHARACTER),
|
||||
$.span({ref: 'koreanCharacterSpan'}, KOREAN_CHARACTER)
|
||||
)
|
||||
|
||||
this.queryGuttersToRender()
|
||||
this.queryMaxLineNumberDigits()
|
||||
@@ -385,11 +365,7 @@ class TextEditorComponent {
|
||||
attributes,
|
||||
dataset,
|
||||
tabIndex: -1,
|
||||
on: {
|
||||
focus: this.didFocus,
|
||||
blur: this.didBlur,
|
||||
mousewheel: this.didMouseWheel
|
||||
}
|
||||
on: {mousewheel: this.didMouseWheel}
|
||||
},
|
||||
$.div(
|
||||
{
|
||||
@@ -531,14 +507,14 @@ class TextEditorComponent {
|
||||
children = [
|
||||
this.renderCursorsAndInput(),
|
||||
this.renderLineTiles(),
|
||||
this.blockDecorationMeasurementAreaVnode,
|
||||
this.characterMeasurementLineVnode,
|
||||
this.renderBlockDecorationMeasurementArea(),
|
||||
this.renderCharacterMeasurementLine(),
|
||||
this.renderPlaceholderText()
|
||||
]
|
||||
} else {
|
||||
children = [
|
||||
this.blockDecorationMeasurementAreaVnode,
|
||||
this.characterMeasurementLineVnode
|
||||
this.renderBlockDecorationMeasurementArea(),
|
||||
this.renderCharacterMeasurementLine()
|
||||
]
|
||||
}
|
||||
|
||||
@@ -667,6 +643,33 @@ class TextEditorComponent {
|
||||
return this.placeholderTextVnode
|
||||
}
|
||||
|
||||
renderCharacterMeasurementLine () {
|
||||
return $.div(
|
||||
{
|
||||
key: 'characterMeasurementLine',
|
||||
ref: 'characterMeasurementLine',
|
||||
className: 'line dummy',
|
||||
style: {position: 'absolute', visibility: 'hidden'}
|
||||
},
|
||||
$.span({ref: 'normalWidthCharacterSpan'}, NORMAL_WIDTH_CHARACTER),
|
||||
$.span({ref: 'doubleWidthCharacterSpan'}, DOUBLE_WIDTH_CHARACTER),
|
||||
$.span({ref: 'halfWidthCharacterSpan'}, HALF_WIDTH_CHARACTER),
|
||||
$.span({ref: 'koreanCharacterSpan'}, KOREAN_CHARACTER)
|
||||
)
|
||||
}
|
||||
|
||||
renderBlockDecorationMeasurementArea () {
|
||||
return $.div({
|
||||
ref: 'blockDecorationMeasurementArea',
|
||||
key: 'blockDecorationMeasurementArea',
|
||||
style: {
|
||||
contain: 'strict',
|
||||
position: 'absolute',
|
||||
visibility: 'hidden'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
renderHiddenInput () {
|
||||
let top, left
|
||||
if (this.hiddenInputPosition) {
|
||||
@@ -1205,6 +1208,8 @@ class TextEditorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Called by TextEditorElement so that focus events can be handled before
|
||||
// the element is attached to the DOM.
|
||||
didFocus () {
|
||||
// This element can be focused from a parent custom element's
|
||||
// attachedCallback before *its* attachedCallback is fired. This protects
|
||||
@@ -1243,6 +1248,9 @@ class TextEditorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Called by TextEditorElement so that this function is always the first
|
||||
// listener to be fired, even if other listeners are bound before creating
|
||||
// the component.
|
||||
didBlur (event) {
|
||||
if (event.relatedTarget === this.refs.hiddenInput) {
|
||||
event.stopImmediatePropagation()
|
||||
@@ -2026,7 +2034,6 @@ class TextEditorComponent {
|
||||
this.disposables.add(model.onDidRemoveGutter(scheduleUpdate))
|
||||
this.disposables.add(model.selectionsMarkerLayer.onDidUpdate(this.didUpdateSelections.bind(this)))
|
||||
this.disposables.add(model.onDidRequestAutoscroll(this.didRequestAutoscroll.bind(this)))
|
||||
this.blockDecorationsToMeasure = new Set()
|
||||
this.disposables.add(model.observeDecorations((decoration) => {
|
||||
if (decoration.getProperties().type === 'block') this.observeBlockDecoration(decoration)
|
||||
}))
|
||||
@@ -2187,7 +2194,7 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
getGutterContainerWidth () {
|
||||
return this.measurements.gutterContainerWidth
|
||||
return (this.measurements) ? this.measurements.gutterContainerWidth : 0
|
||||
}
|
||||
|
||||
getLineNumberGutterWidth () {
|
||||
@@ -2262,6 +2269,7 @@ class TextEditorComponent {
|
||||
if (scrollTop !== this.scrollTop) {
|
||||
this.scrollTopPending = true
|
||||
this.scrollTop = scrollTop
|
||||
this.element.emitter.emit('did-change-scroll-top', scrollTop)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -2290,6 +2298,7 @@ class TextEditorComponent {
|
||||
if (scrollLeft !== this.scrollLeft) {
|
||||
this.scrollLeftPending = true
|
||||
this.scrollLeft = scrollLeft
|
||||
this.element.emitter.emit('did-change-scroll-left', scrollLeft)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
||||
@@ -5,7 +5,6 @@ const dedent = require('dedent')
|
||||
class TextEditorElement extends HTMLElement {
|
||||
initialize (component) {
|
||||
this.component = component
|
||||
this.emitter = new Emitter()
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -19,27 +18,85 @@ class TextEditorElement extends HTMLElement {
|
||||
return this
|
||||
}
|
||||
|
||||
createdCallback () {
|
||||
this.emitter = new Emitter()
|
||||
this.initialText = this.textContent
|
||||
this.tabIndex = -1
|
||||
this.addEventListener('focus', (event) => this.getComponent().didFocus(event))
|
||||
this.addEventListener('blur', (event) => this.getComponent().didBlur(event))
|
||||
}
|
||||
|
||||
attachedCallback () {
|
||||
this.getComponent().didAttach()
|
||||
this.emitter.emit('did-attach')
|
||||
this.updateModelFromAttributes()
|
||||
}
|
||||
|
||||
detachedCallback () {
|
||||
this.emitter.emit('did-detach')
|
||||
this.getComponent().didDetach()
|
||||
}
|
||||
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (this.component) {
|
||||
switch (name) {
|
||||
case 'mini':
|
||||
this.getModel().update({mini: newValue != null})
|
||||
break;
|
||||
case 'placeholder-text':
|
||||
this.getModel().update({placeholderText: newValue})
|
||||
break;
|
||||
case 'gutter-hidden':
|
||||
this.getModel().update({isVisible: newValue != null})
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel () {
|
||||
return this.getComponent().props.model
|
||||
}
|
||||
|
||||
setModel (model) {
|
||||
this.getComponent().setModel(model)
|
||||
this.getComponent().update({model})
|
||||
this.updateModelFromAttributes()
|
||||
}
|
||||
|
||||
updateModelFromAttributes () {
|
||||
const props = {
|
||||
mini: this.hasAttribute('mini'),
|
||||
placeholderText: this.getAttribute('placeholder-text'),
|
||||
}
|
||||
if (this.hasAttribute('gutter-hidden')) props.lineNumberGutterVisible = false
|
||||
|
||||
this.getModel().update(props)
|
||||
if (this.initialText) this.getModel().setText(this.initialText)
|
||||
}
|
||||
|
||||
onDidAttach (callback) {
|
||||
return this.emitter.on('did-attach', callback)
|
||||
}
|
||||
|
||||
onDidDetach (callback) {
|
||||
return this.emitter.on('did-detach', callback)
|
||||
}
|
||||
|
||||
setWidth (width) {
|
||||
this.style.width = this.getComponent().getGutterContainerWidth() + width + 'px'
|
||||
}
|
||||
|
||||
getWidth () {
|
||||
this.offsetWidth - this.getComponent().getGutterContainerWidth()
|
||||
}
|
||||
|
||||
setHeight (height) {
|
||||
this.style.height = height + 'px'
|
||||
}
|
||||
|
||||
getHeight () {
|
||||
return this.offsetHeight
|
||||
}
|
||||
|
||||
onDidChangeScrollLeft (callback) {
|
||||
return this.emitter.on('did-change-scroll-left', callback)
|
||||
}
|
||||
@@ -52,14 +109,26 @@ class TextEditorElement extends HTMLElement {
|
||||
return this.getComponent().getBaseCharacterWidth()
|
||||
}
|
||||
|
||||
getMaxScrollTop () {
|
||||
return this.getComponent().getMaxScrollTop()
|
||||
}
|
||||
|
||||
getScrollTop () {
|
||||
return this.getComponent().getScrollTop()
|
||||
}
|
||||
|
||||
setScrollTop (scrollTop) {
|
||||
this.getComponent().setScrollTop(scrollTop)
|
||||
}
|
||||
|
||||
getScrollLeft () {
|
||||
return this.getComponent().getScrollLeft()
|
||||
}
|
||||
|
||||
setScrollLeft (scrollLeft) {
|
||||
this.getComponent().setScrollLeft(scrollLeft)
|
||||
}
|
||||
|
||||
hasFocus () {
|
||||
return this.getComponent().focused
|
||||
}
|
||||
@@ -81,6 +150,10 @@ class TextEditorElement extends HTMLElement {
|
||||
return updatedSynchronously
|
||||
}
|
||||
|
||||
isUpdatedSynchronously () {
|
||||
return this.component ? this.component.updatedSynchronously : this.updatedSynchronously
|
||||
}
|
||||
|
||||
// Experimental: Invalidate the passed block {Decoration}'s dimensions,
|
||||
// forcing them to be recalculated and the surrounding content to be adjusted
|
||||
// on the next animation frame.
|
||||
|
||||
@@ -321,6 +321,7 @@ class TextEditor extends Model
|
||||
@cursorLineDecorations = null
|
||||
else
|
||||
@decorateCursorLine()
|
||||
@component?.scheduleUpdate()
|
||||
|
||||
when 'placeholderText'
|
||||
if value isnt @placeholderText
|
||||
|
||||
Reference in New Issue
Block a user