Extensions have a src and specs directory now. Move existing extension specs.

Move the extensions spec code inside of the extension's spec directory. Move source code to the extension's src directory
This commit is contained in:
Corey Johnson
2012-10-25 11:45:58 -07:00
parent 6870f21ca5
commit 2af29c9934
68 changed files with 346 additions and 84 deletions

View File

@@ -1 +1 @@
module.exports = require 'tree-view/tree-view'
module.exports = require 'tree-view/src/tree-view'

View File

@@ -0,0 +1,835 @@
$ = require 'jquery'
_ = require 'underscore'
TreeView = require 'tree-view'
RootView = require 'root-view'
Directory = require 'directory'
Native = require 'native'
fs = require 'fs'
describe "TreeView", ->
[rootView, project, treeView, sampleJs, sampleTxt] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/tree-view'))
project = rootView.project
rootView.activateExtension(TreeView)
treeView = rootView.find(".tree-view").view()
treeView.root = treeView.find('> li:first').view()
sampleJs = treeView.find('.file:contains(tree-view.js)')
sampleTxt = treeView.find('.file:contains(tree-view.txt)')
expect(treeView.root.directory.subscriptionCount()).toBeGreaterThan 0
afterEach ->
rootView.deactivate()
describe ".initialize(project)", ->
it "renders the root of the project and its contents alphabetically with subdirectories first in a collapsed state", ->
expect(treeView.root.find('> .header .disclosure-arrow')).toHaveText('')
expect(treeView.root.find('> .header .name')).toHaveText('tree-view/')
rootEntries = treeView.root.find('.entries')
subdir0 = rootEntries.find('> li:eq(0)')
expect(subdir0.find('.disclosure-arrow')).toHaveText('')
expect(subdir0.find('.name')).toHaveText('dir1/')
expect(subdir0.find('.entries')).not.toExist()
subdir2 = rootEntries.find('> li:eq(1)')
expect(subdir2.find('.disclosure-arrow')).toHaveText('')
expect(subdir2.find('.name')).toHaveText('dir2/')
expect(subdir2.find('.entries')).not.toExist()
expect(rootEntries.find('> .file:contains(tree-view.js)')).toExist()
expect(rootEntries.find('> .file:contains(tree-view.txt)')).toExist()
it "selects the rootview", ->
expect(treeView.selectedEntry()).toEqual treeView.root
describe "when the project has no path", ->
beforeEach ->
rootView.deactivate()
rootView = new RootView
rootView.activateExtension(TreeView)
treeView = rootView.find(".tree-view").view()
it "does not create a root node", ->
expect(treeView.root).not.toExist()
it "serializes without throwing an exception", ->
expect(-> treeView.serialize()).not.toThrow()
it "creates a root view when the project path is created", ->
rootView.open(require.resolve('fixtures/sample.js'))
expect(treeView.root.getPath()).toBe require.resolve('fixtures')
expect(treeView.root.parent()).toMatchSelector(".tree-view")
oldRoot = treeView.root
rootView.project.setPath('/tmp')
expect(treeView.root).not.toEqual oldRoot
expect(oldRoot.hasParent()).toBeFalsy()
describe "when the prototypes deactivate method is called", ->
it "calls the deactivate on tree view instance", ->
spyOn(treeView, "deactivate").andCallThrough()
rootView.deactivateExtension(TreeView)
expect(treeView.deactivate).toHaveBeenCalled()
describe "serialization", ->
[newRootView, newTreeView] = []
afterEach ->
newRootView?.deactivate()
it "restores expanded directories and selected file when deserialized", ->
treeView.find('.directory:contains(dir1)').click()
sampleJs.click()
newRootView = RootView.deserialize(rootView.serialize())
rootView.deactivate() # Deactivates previous TreeView
newRootView.activateExtension(TreeView)
newTreeView = newRootView.find(".tree-view").view()
expect(newTreeView).toExist()
expect(newTreeView.selectedEntry()).toMatchSelector(".file:contains(tree-view.js)")
expect(newTreeView.find(".directory:contains(dir1)")).toHaveClass("expanded")
it "restores the focus state of the tree view", ->
rootView.attachToDom()
treeView.focus()
expect(treeView).toMatchSelector ':focus'
newRootView = RootView.deserialize(rootView.serialize())
rootView.deactivate() # Deactivates previous TreeView
newRootView.attachToDom()
newRootView.activateExtension(TreeView)
newTreeView = newRootView.find(".tree-view").view()
expect(newTreeView).toMatchSelector ':focus'
it "restores the scroll top when toggled", ->
rootView.height(5)
rootView.attachToDom()
expect(treeView).toBeVisible()
treeView.focus()
treeView.scrollTop(10)
expect(treeView.scrollTop()).toBe(10)
rootView.trigger 'tree-view:toggle'
expect(treeView).toBeHidden()
rootView.trigger 'tree-view:toggle'
expect(treeView).toBeVisible()
expect(treeView.scrollTop()).toBe(10)
describe "when tree-view:toggle is triggered on the root view", ->
beforeEach ->
rootView.attachToDom()
describe "when the tree view is visible", ->
beforeEach ->
expect(treeView).toBeVisible()
describe "when the tree view is focused", ->
it "hides the tree view", ->
treeView.focus()
rootView.trigger 'tree-view:toggle'
expect(treeView).toBeHidden()
describe "when the tree view is not focused", ->
it "shifts focus to the tree view", ->
rootView.open() # When we call focus below, we want an editor to become focused
rootView.focus()
rootView.trigger 'tree-view:toggle'
expect(treeView).toBeVisible()
expect(treeView).toMatchSelector(':focus')
describe "when the tree view is hidden", ->
it "shows and focuses the tree view", ->
treeView.detach()
rootView.trigger 'tree-view:toggle'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView).toMatchSelector(':focus')
describe "when tree-view:reveal-current-file is triggered on the root view", ->
beforeEach ->
treeView.detach()
spyOn(treeView, 'focus')
describe "if the current file has a path", ->
it "shows and focuses the tree view and selects the file", ->
rootView.open('dir1/file1')
rootView.trigger 'tree-view:reveal-active-file'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.focus).toHaveBeenCalled()
expect(treeView.selectedEntry().getPath()).toMatch /dir1\/file1$/
describe "if the current file has no path", ->
it "shows and focuses the tree view, but does not attempt to select a specific file", ->
rootView.open()
expect(rootView.getActiveEditSession().getPath()).toBeUndefined()
rootView.trigger 'tree-view:reveal-active-file'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.focus).toHaveBeenCalled()
describe "if there is no editor open", ->
it "shows and focuses the tree view, but does not attempt to select a specific file", ->
expect(rootView.getActiveEditSession()).toBeUndefined()
rootView.trigger 'tree-view:reveal-active-file'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView.focus).toHaveBeenCalled()
describe "when tool-panel:unfocus is triggered on the tree view", ->
it "surrenders focus to the root view but remains open", ->
rootView.open() # When we trigger 'tool-panel:unfocus' below, we want an editor to become focused
rootView.attachToDom()
treeView.focus()
expect(treeView).toMatchSelector(':focus')
treeView.trigger 'tool-panel:unfocus'
expect(treeView).toBeVisible()
expect(treeView).not.toMatchSelector(':focus')
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
describe "when a directory's disclosure arrow is clicked", ->
it "expands / collapses the associated directory", ->
subdir = treeView.root.find('.entries > li:contains(dir1/)').view()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).not.toExist()
subdir.disclosureArrow.click()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).toExist()
subdir.disclosureArrow.click()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).not.toExist()
it "restores the expansion state of descendant directories", ->
child = treeView.root.find('.entries > li:contains(dir1/)').view()
child.disclosureArrow.click()
grandchild = child.find('.entries > li:contains(sub-dir1/)').view()
grandchild.disclosureArrow.click()
treeView.root.disclosureArrow.click()
expect(treeView.root.find('.entries')).not.toExist()
treeView.root.disclosureArrow.click()
# previously expanded descendants remain expanded
expect(treeView.root.find('> .entries > li:contains(dir1/) > .entries > li:contains(sub-dir1/) > .entries').length).toBe 1
# collapsed descendants remain collapsed
expect(treeView.root.find('> .entries > li.contains(dir2/) > .entries')).not.toExist()
it "when collapsing a directory, removes change subscriptions from the collapsed directory and its descendants", ->
child = treeView.root.entries.find('li:contains(dir1/)').view()
child.disclosureArrow.click()
grandchild = child.entries.find('li:contains(sub-dir1/)').view()
grandchild.disclosureArrow.click()
expect(treeView.root.directory.subscriptionCount()).toBe 1
expect(child.directory.subscriptionCount()).toBe 1
expect(grandchild.directory.subscriptionCount()).toBe 1
treeView.root.disclosureArrow.click()
expect(treeView.root.directory.subscriptionCount()).toBe 0
expect(child.directory.subscriptionCount()).toBe 0
expect(grandchild.directory.subscriptionCount()).toBe 0
describe "when a file is single-clicked", ->
it "selects the files and opens it in the active editor, without changing focus", ->
expect(rootView.getActiveEditor()).toBeUndefined()
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
sampleTxt.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.txt')
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
describe "when a file is double-clicked", ->
it "selects the file and opens it in the active editor on the first click, then changes focus to the active editor on the second", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
sampleJs.trigger clickEvent(originalEvent: { detail: 2 })
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
describe "when a directory is single-clicked", ->
it "is selected", ->
subdir = treeView.root.find('.directory:first').view()
subdir.trigger clickEvent(originalEvent: { detail: 1 })
expect(subdir).toHaveClass 'selected'
describe "when a directory is double-clicked", ->
it "toggles the directory expansion state and does not change the focus to the editor", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
subdir = treeView.root.find('.directory:first').view()
subdir.trigger clickEvent(originalEvent: { detail: 1 })
expect(subdir).toHaveClass 'selected'
subdir.trigger clickEvent(originalEvent: { detail: 2 })
expect(subdir).toHaveClass 'expanded'
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
describe "when a new file is opened in the active editor", ->
it "is selected in the tree view if the file's entry visible", ->
sampleJs.click()
rootView.open(require.resolve('fixtures/tree-view/tree-view.txt'))
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
it "selected a file's parent dir if the file's entry is not visible", ->
rootView.open(require.resolve('fixtures/tree-view/dir1/sub-dir1/sub-file1'))
dirView = treeView.root.find('.directory:contains(dir1)').view()
expect(dirView).toHaveClass 'selected'
describe "when a different editor becomes active", ->
it "selects the file in that is open in that editor", ->
sampleJs.click()
leftEditor = rootView.getActiveEditor()
rightEditor = leftEditor.splitRight()
sampleTxt.click()
expect(sampleTxt).toHaveClass('selected')
leftEditor.focus()
expect(sampleJs).toHaveClass('selected')
describe "keyboard navigation", ->
afterEach ->
expect(treeView.find('.selected').length).toBeLessThan 2
describe "core:move-down", ->
describe "when a collapsed directory is selected", ->
it "skips to the next directory", ->
treeView.root.find('.directory:eq(0)').click()
treeView.trigger 'core:move-down'
expect(treeView.root.find('.directory:eq(1)')).toHaveClass 'selected'
describe "when an expanded directory is selected", ->
it "selects the first entry of the directory", ->
subdir = treeView.root.find('.directory:eq(1)').view()
subdir.expand()
subdir.click()
treeView.trigger 'core:move-down'
expect(subdir.entries.find('.entry:first')).toHaveClass 'selected'
describe "when the last entry of an expanded directory is selected", ->
it "selects the entry after its parent directory", ->
subdir1 = treeView.root.find('.directory:eq(1)').view()
subdir1.expand()
subdir1.entries.find('.entry:last').click()
treeView.trigger 'core:move-down'
expect(treeView.root.find('.entries > .entry:eq(2)')).toHaveClass 'selected'
describe "when the last directory of another last directory is selected", ->
[nested, nested2] = []
beforeEach ->
nested = treeView.root.find('.directory:eq(2)').view()
expect(nested.find('.header').text()).toContain 'nested/'
nested.expand()
nested2 = nested.entries.find('.entry:last').view()
nested2.click()
describe "when the directory is collapsed", ->
it "selects the entry after its grandparent directory", ->
treeView.trigger 'core:move-down'
expect(nested.next()).toHaveClass 'selected'
describe "when the directory is expanded", ->
it "selects the entry after its grandparent directory", ->
nested2.expand()
nested2.find('.file').remove() # kill the .gitkeep file, which has to be there but screws the test
treeView.trigger 'core:move-down'
expect(nested.next()).toHaveClass 'selected'
describe "when the last entry of the last directory is selected", ->
it "does not change the selection", ->
lastEntry = treeView.root.find('> .entries .entry:last')
lastEntry.click()
treeView.trigger 'core:move-down'
expect(lastEntry).toHaveClass 'selected'
describe "core:move-up", ->
describe "when there is an expanded directory before the currently selected entry", ->
it "selects the last entry in the expanded directory", ->
lastDir = treeView.root.find('.directory:last').view()
fileAfterDir = lastDir.next().view()
lastDir.expand()
fileAfterDir.click()
treeView.trigger 'core:move-up'
expect(lastDir.find('.entry:last')).toHaveClass 'selected'
describe "when there is an entry before the currently selected entry", ->
it "selects the previous entry", ->
lastEntry = treeView.root.find('.entry:last')
lastEntry.click()
treeView.trigger 'core:move-up'
expect(lastEntry.prev()).toHaveClass 'selected'
describe "when there is no entry before the currently selected entry, but there is a parent directory", ->
it "selects the parent directory", ->
subdir = treeView.root.find('.directory:first').view()
subdir.expand()
subdir.find('> .entries > .entry:first').click()
treeView.trigger 'core:move-up'
expect(subdir).toHaveClass 'selected'
describe "when there is no parent directory or previous entry", ->
it "does not change the selection", ->
treeView.root.click()
treeView.trigger 'core:move-up'
expect(treeView.root).toHaveClass 'selected'
describe "core:move-to-top", ->
it "scrolls to the top", ->
treeView.height(100)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
expect(treeView.scrollTop()).toBe 0
entryCount = treeView.find(".entry").length
_.times entryCount, -> treeView.moveDown()
expect(treeView.scrollTop()).toBeGreaterThan 0
treeView.trigger 'core:move-to-top'
expect(treeView.scrollTop()).toBe 0
describe "core:move-to-bottom", ->
it "scrolls to the bottom", ->
treeView.height(100)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
expect(treeView.scrollTop()).toBe 0
treeView.trigger 'core:move-to-bottom'
expect(treeView.scrollBottom()).toBe treeView.prop('scrollHeight')
describe "core:page-up", ->
it "scrolls up a page", ->
treeView.height(5)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
expect(treeView.scrollTop()).toBe 0
treeView.scrollToBottom()
scrollTop = treeView.scrollTop()
expect(scrollTop).toBeGreaterThan 0
treeView.trigger 'core:page-up'
expect(treeView.scrollTop()).toBe scrollTop - treeView.height()
describe "core:page-down", ->
it "scrolls down a page", ->
treeView.height(5)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
expect(treeView.scrollTop()).toBe 0
treeView.trigger 'core:page-down'
expect(treeView.scrollTop()).toBe treeView.height()
describe "movement outside of viewable region", ->
it "scrolls the tree view to the selected item", ->
treeView.height(100)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
treeView.moveDown()
expect(treeView.scrollTop()).toBe 0
entryCount = treeView.find(".entry").length
_.times entryCount, -> treeView.moveDown()
expect(treeView.scrollBottom()).toBe treeView.prop('scrollHeight')
_.times entryCount, -> treeView.moveUp()
expect(treeView.scrollTop()).toBe 0
describe "tree-view:expand-directory", ->
describe "when a directory entry is selected", ->
it "expands the current directory", ->
subdir = treeView.root.find('.directory:first')
subdir.click()
expect(subdir).not.toHaveClass 'expanded'
treeView.trigger 'tree-view:expand-directory'
expect(subdir).toHaveClass 'expanded'
describe "when a file entry is selected", ->
it "does nothing", ->
treeView.root.find('.file').click()
treeView.trigger 'tree-view:expand-directory'
describe "tree-view:collapse-directory", ->
subdir = null
beforeEach ->
subdir = treeView.root.find('> .entries > .directory').eq(0).view()
subdir.expand()
describe "when an expanded directory is selected", ->
it "collapses the selected directory", ->
expect(subdir).toHaveClass 'expanded'
subdir.click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(treeView.root).toHaveClass 'expanded'
describe "when a collapsed directory is selected", ->
it "collapses and selects the selected directory's parent directory", ->
subdir.find('.directory').click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(subdir).toHaveClass 'selected'
expect(treeView.root).toHaveClass 'expanded'
describe "when collapsed root directory is selected", ->
it "does not raise an error", ->
treeView.root.collapse()
treeView.selectEntry(treeView.root)
treeView.trigger 'tree-view:collapse-directory'
describe "when a file is selected", ->
it "collapses and selects the selected file's parent directory", ->
subdir.find('.file').click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(subdir).toHaveClass 'selected'
expect(treeView.root).toHaveClass 'expanded'
describe "tree-view:open-selected-entry", ->
describe "when a file is selected", ->
it "opens the file in the editor and focuses it", ->
treeView.root.find('.file:contains(tree-view.js)').click()
treeView.root.trigger 'tree-view:open-selected-entry'
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/tree-view/tree-view.js')
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
describe "when a directory is selected", ->
it "expands or collapses the directory", ->
subdir = treeView.root.find('.directory').first()
subdir.click()
expect(subdir).not.toHaveClass 'expanded'
treeView.root.trigger 'tree-view:open-selected-entry'
expect(subdir).toHaveClass 'expanded'
treeView.root.trigger 'tree-view:open-selected-entry'
expect(subdir).not.toHaveClass 'expanded'
describe "when nothing is selected", ->
it "does nothing", ->
treeView.root.trigger 'tree-view:open-selected-entry'
expect(rootView.getActiveEditor()).toBeUndefined()
describe "file modification", ->
[dirView, fileView, rootDirPath, dirPath, filePath] = []
beforeEach ->
rootView.deactivate()
rootDirPath = "/tmp/atom-tests"
fs.remove(rootDirPath) if fs.exists(rootDirPath)
dirPath = fs.join(rootDirPath, "test-dir")
filePath = fs.join(dirPath, "test-file.txt")
fs.makeDirectory(rootDirPath)
fs.makeDirectory(dirPath)
fs.write(filePath, "doesn't matter")
rootView = new RootView(rootDirPath)
project = rootView.project
rootView.activateExtension(TreeView)
treeView = rootView.find(".tree-view").view()
dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
dirView.expand()
fileView = treeView.find('.file:contains(test-file.txt)').view()
afterEach ->
fs.remove(rootDirPath) if fs.exists(rootDirPath)
describe "tree-view:add", ->
addDialog = null
beforeEach ->
fileView.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
describe "when a file is selected", ->
it "opens an add dialog with the file's current directory path populated", ->
expect(addDialog).toExist()
expect(addDialog.prompt.text()).toBeTruthy()
expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
expect(addDialog.miniEditor.isFocused).toBeTruthy()
describe "when parent directory of the selected file changes", ->
it "active file is still shown as selected in the tree view", ->
directoryChangeHandler = jasmine.createSpy("directory-change")
dirView.on "tree-view:directory-modified", directoryChangeHandler
dirView.directory.trigger 'contents-change'
expect(directoryChangeHandler).toHaveBeenCalled()
expect(treeView.find('.selected').text()).toBe fs.base(filePath)
describe "when the path without a trailing '/' is changed and confirmed", ->
describe "when no file exists at that location", ->
it "add a file, closes the dialog and selects the file in the tree-view", ->
newPath = fs.join(dirPath, "new-test-file.txt")
addDialog.miniEditor.insertText(fs.base(newPath))
addDialog.trigger 'core:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isFile(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
expect(rootView.getActiveEditor().getPath()).toBe newPath
waitsFor "tree view to be updated", ->
dirView.entries.find("> .file").length > 1
runs ->
expect(treeView.find('.selected').text()).toBe fs.base(newPath)
describe "when a file already exists at that location", ->
it "shows an error message and does not close the dialog", ->
newPath = fs.join(dirPath, "new-test-file.txt")
fs.write(newPath, '')
addDialog.miniEditor.insertText(fs.base(newPath))
addDialog.trigger 'core:confirm'
expect(addDialog.prompt.text()).toContain 'Error'
expect(addDialog.prompt.text()).toContain 'already exists'
expect(addDialog.prompt).toHaveClass('error')
expect(addDialog.hasParent()).toBeTruthy()
describe "when the path with a trailing '/' is changed and confirmed", ->
describe "when no file or directory exists at the given path", ->
it "adds a directory and closes the dialog", ->
treeView.attachToDom()
newPath = fs.join(dirPath, "new/dir")
addDialog.miniEditor.insertText("new/dir/")
addDialog.trigger 'core:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isDirectory(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
expect(rootView.getActiveEditor().getPath()).not.toBe newPath
expect(treeView).toMatchSelector(':focus')
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
expect(dirView.find('.directory.selected:contains(new/)').length).toBe(1)
it "selects the created directory", ->
treeView.attachToDom()
newPath = fs.join(dirPath, "new2/")
addDialog.miniEditor.insertText("new2/")
addDialog.trigger 'core:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isDirectory(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
expect(rootView.getActiveEditor().getPath()).not.toBe newPath
expect(treeView).toMatchSelector(':focus')
expect(rootView.getActiveEditor().isFocused).toBeFalsy()
expect(dirView.find('.directory.selected:contains(new2/)').length).toBe(1)
describe "when a file or directory already exists at the given path", ->
it "shows an error message and does not close the dialog", ->
newPath = fs.join(dirPath, "new-dir")
fs.makeDirectory(newPath)
addDialog.miniEditor.insertText("new-dir/")
addDialog.trigger 'core:confirm'
expect(addDialog.prompt.text()).toContain 'Error'
expect(addDialog.prompt.text()).toContain 'already exists'
expect(addDialog.prompt).toHaveClass('error')
expect(addDialog.hasParent()).toBeTruthy()
describe "when 'core:cancel' is triggered on the add dialog", ->
it "removes the dialog and focuses the tree view", ->
treeView.attachToDom()
addDialog.trigger 'core:cancel'
expect(addDialog.parent()).not.toExist()
expect(treeView).toMatchSelector(':focus')
describe "when the add dialog's editor loses focus", ->
it "removes the dialog and focuses root view", ->
rootView.attachToDom()
rootView.focus()
expect(addDialog.parent()).not.toExist()
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
describe "when a directory is selected", ->
it "opens an add dialog with the directory's path populated", ->
addDialog.cancel()
dirView.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
expect(addDialog).toExist()
expect(addDialog.prompt.text()).toBeTruthy()
expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
expect(addDialog.miniEditor.isFocused).toBeTruthy()
describe "when the root directory is selected", ->
it "opens an add dialog with no path populated", ->
addDialog.cancel()
treeView.root.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
expect(addDialog.miniEditor.getText().length).toBe 0
describe "tree-view:move", ->
describe "when a file is selected", ->
moveDialog = null
beforeEach ->
fileView.click()
treeView.trigger "tree-view:move"
moveDialog = rootView.find(".tree-view-dialog").view()
it "opens a move dialog with the file's current path (excluding extension) populated", ->
extension = fs.extension(filePath)
fileNameWithoutExtension = fs.base(filePath, extension)
expect(moveDialog).toExist()
expect(moveDialog.prompt.text()).toBe "Enter the new path for the file:"
expect(moveDialog.miniEditor.getText()).toBe(project.relativize(filePath))
expect(moveDialog.miniEditor.getSelectedText()).toBe fs.base(fileNameWithoutExtension)
expect(moveDialog.miniEditor.isFocused).toBeTruthy()
describe "when the path is changed and confirmed", ->
describe "when all the directories along the new path exist", ->
it "moves the file, updates the tree view, and closes the dialog", ->
newPath = fs.join(rootDirPath, 'renamed-test-file.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'core:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.exists(filePath)).toBeFalsy()
expect(moveDialog.parent()).not.toExist()
waitsFor "tree view to update", ->
treeView.root.find('> .entries > .file:contains(renamed-test-file.txt)').length > 0
runs ->
dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
dirView.expand()
expect(dirView.entries.children().length).toBe 0
describe "when the directories along the new path don't exist", ->
it "creates the target directory before moving the file", ->
newPath = fs.join(rootDirPath, 'new/directory', 'renamed-test-file.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'core:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.exists(filePath)).toBeFalsy()
waits 50 # TODO: remove this workaround once we fix the race condition in fs events
describe "when a file or directory already exists at the target path", ->
it "shows an error message and does not close the dialog", ->
runs ->
fs.write(fs.join(rootDirPath, 'target.txt'), '')
newPath = fs.join(rootDirPath, 'target.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'core:confirm'
expect(moveDialog.prompt.text()).toContain 'Error'
expect(moveDialog.prompt.text()).toContain 'already exists'
expect(moveDialog.prompt).toHaveClass('error')
expect(moveDialog.hasParent()).toBeTruthy()
describe "when 'core:cancel' is triggered on the move dialog", ->
it "removes the dialog and focuses the tree view", ->
treeView.attachToDom()
moveDialog.trigger 'core:cancel'
expect(moveDialog.parent()).not.toExist()
expect(treeView).toMatchSelector(':focus')
describe "when the move dialog's editor loses focus", ->
it "removes the dialog and focuses root view", ->
rootView.attachToDom()
rootView.focus()
expect(moveDialog.parent()).not.toExist()
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
describe "tree-view:remove", ->
it "shows the native alert dialog", ->
fileView.click()
spyOn(atom, 'confirm')
treeView.trigger 'tree-view:remove'
expect(atom.confirm).toHaveBeenCalled()
describe "file system events", ->
temporaryFilePath = null
beforeEach ->
temporaryFilePath = fs.join(require.resolve('fixtures/tree-view'), 'temporary')
if fs.exists(temporaryFilePath)
fs.remove(temporaryFilePath)
waits(20)
afterEach ->
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
describe "when a file is added or removed in an expanded directory", ->
it "updates the directory view to display the directory's new contents", ->
entriesCountBefore = null
runs ->
expect(fs.exists(temporaryFilePath)).toBeFalsy()
entriesCountBefore = treeView.root.entries.find('.entry').length
fs.write temporaryFilePath, 'hi'
waitsFor "directory view contens to refresh", ->
treeView.root.entries.find('.entry').length == entriesCountBefore + 1
runs ->
expect(treeView.root.entries.find('.entry').length).toBe entriesCountBefore + 1
expect(treeView.root.entries.find('.file:contains(temporary)')).toExist()
fs.remove(temporaryFilePath)
waitsFor "directory view contens to refresh", ->
treeView.root.entries.find('.entry').length == entriesCountBefore

View File

@@ -1,5 +1,5 @@
{View, $$} = require 'space-pen'
FileView = require 'tree-view/file-view'
FileView = require 'tree-view/src/file-view'
Directory = require 'directory'
$ = require 'jquery'

View File

@@ -0,0 +1,292 @@
{View, $$} = require 'space-pen'
ScrollView = require 'scroll-view'
Directory = require 'directory'
DirectoryView = require 'tree-view/src/directory-view'
FileView = require 'tree-view/src/file-view'
Dialog = require 'tree-view/src/dialog'
Native = require 'native'
fs = require 'fs'
$ = require 'jquery'
_ = require 'underscore'
module.exports =
class TreeView extends ScrollView
@activate: (rootView, state) ->
requireStylesheet 'tree-view.css'
if state
@instance = TreeView.deserialize(state, rootView)
else
@instance = new TreeView(rootView)
@instance.attach()
@deactivate: () ->
@instance.deactivate()
@serialize: ->
@instance.serialize()
@content: (rootView) ->
@div class: 'tree-view tool-panel', tabindex: -1, =>
if rootView.project.getRootDirectory()
@subview 'root', new DirectoryView(directory: rootView.project.getRootDirectory(), isExpanded: true)
@deserialize: (state, rootView) ->
treeView = new TreeView(rootView)
treeView.root.deserializeEntryExpansionStates(state.directoryExpansionStates)
treeView.selectEntryForPath(state.selectedPath)
treeView.focusAfterAttach = state.hasFocus
treeView.scrollTopAfterAttach = state.scrollTop
treeView.attach() if state.attached
treeView
root: null
focusAfterAttach: false
scrollTopAfterAttach: -1
selectedPath: null
initialize: (@rootView) ->
super
@on 'click', '.entry', (e) => @entryClicked(e)
@command 'core:move-up', => @moveUp()
@command 'core:move-down', => @moveDown()
@command 'core:close', => @detatch()
@command 'tree-view:expand-directory', => @expandDirectory()
@command 'tree-view:collapse-directory', => @collapseDirectory()
@command 'tree-view:open-selected-entry', => @openSelectedEntry(true)
@command 'tree-view:move', => @moveSelectedEntry()
@command 'tree-view:add', => @add()
@command 'tree-view:remove', => @removeSelectedEntry()
@command 'tool-panel:unfocus', => @rootView.focus()
@command 'tree-view:directory-modified', =>
if @hasFocus()
@selectEntryForPath(@selectedPath) if @selectedPath
else
@selectActiveFile()
@rootView.command 'tree-view:toggle', => @toggle()
@rootView.command 'tree-view:reveal-active-file', => @revealActiveFile()
@rootView.on 'active-editor-path-change', => @selectActiveFile()
@rootView.project.on 'path-change', => @updateRoot()
@selectEntry(@root) if @root
afterAttach: (onDom) ->
@focus() if @focusAfterAttach
@scrollTop(@scrollTopAfterAttach) if @scrollTopAfterAttach > 0
serialize: ->
directoryExpansionStates: @root?.serializeEntryExpansionStates()
selectedPath: @selectedEntry()?.getPath()
hasFocus: @hasFocus()
attached: @hasParent()
scrollTop: @scrollTop()
deactivate: ->
@root?.unwatchEntries()
toggle: ->
if @hasFocus()
@detach()
else
if @hasParent()
@focus()
else
@attach()
attach: ->
@rootView.horizontal.prepend(this)
@focus()
detach: ->
@scrollTopAfterAttach = @scrollTop()
super
@rootView.focus()
hasFocus: ->
@is(':focus')
entryClicked: (e) ->
entry = $(e.currentTarget).view()
switch e.originalEvent?.detail ? 1
when 1
@selectEntry(entry)
@openSelectedEntry(false) if (entry instanceof FileView)
when 2
if entry.is('.selected.file')
@rootView.getActiveEditor().focus()
else if entry.is('.selected.directory')
entry.toggleExpansion()
false
updateRoot: ->
@root?.remove()
if @rootView.project.getRootDirectory()
@root = new DirectoryView(directory: @rootView.project.getRootDirectory(), isExpanded: true)
@append(@root)
else
@root = null
selectActiveFile: ->
activeFilePath = @rootView.getActiveEditor()?.getPath()
@selectEntryForPath(activeFilePath) if activeFilePath
revealActiveFile: ->
@attach()
@focus()
return unless activeFilePath = @rootView.getActiveEditor()?.getPath()
project = @rootView.project
activePathComponents = project.relativize(activeFilePath).split('/')
currentPath = project.getPath().replace(/\/$/, '')
for pathComponent in activePathComponents
currentPath += '/' + pathComponent
entry = @entryForPath(currentPath)
if entry.hasClass('directory')
entry.expand()
else
@selectEntry(entry)
entryForPath: (path) ->
fn = (bestMatchEntry, element) ->
entry = $(element).view()
regex = new RegExp("^" + _.escapeRegExp(entry.getPath()))
if regex.test(path) and entry.getPath().length > bestMatchEntry.getPath().length
entry
else
bestMatchEntry
@find(".entry").toArray().reduce(fn, @root)
selectEntryForPath: (path) ->
@selectEntry(@entryForPath(path))
moveDown: ->
selectedEntry = @selectedEntry()
if selectedEntry
if selectedEntry.is('.expanded.directory')
return if @selectEntry(selectedEntry.find('.entry:first'))
until @selectEntry(selectedEntry.next())
selectedEntry = selectedEntry.parents('.entry:first')
break unless selectedEntry.length
else
@selectEntry(@root)
@scrollToEntry(@selectedEntry())
moveUp: ->
selectedEntry = @selectedEntry()
if selectedEntry
if previousEntry = @selectEntry(selectedEntry.prev())
if previousEntry.is('.expanded.directory')
@selectEntry(previousEntry.find('.entry:last'))
else
@selectEntry(selectedEntry.parents('.directory').first())
else
@selectEntry(@find('.entry').last())
@scrollToEntry(@selectedEntry())
expandDirectory: ->
selectedEntry = @selectedEntry()
selectedEntry.view().expand() if (selectedEntry instanceof DirectoryView)
collapseDirectory: ->
selectedEntry = @selectedEntry()
if directory = selectedEntry.closest('.expanded.directory').view()
directory.collapse()
@selectEntry(directory)
openSelectedEntry: (changeFocus) ->
selectedEntry = @selectedEntry()
if (selectedEntry instanceof DirectoryView)
selectedEntry.view().toggleExpansion()
else if (selectedEntry instanceof FileView)
@rootView.open(selectedEntry.getPath(), { changeFocus })
moveSelectedEntry: ->
entry = @selectedEntry()
return unless entry
oldPath = entry.getPath()
dialog = new Dialog
prompt: "Enter the new path for the file:"
path: @rootView.project.relativize(oldPath)
select: true
onConfirm: (newPath) =>
newPath = @rootView.project.resolve(newPath)
directoryPath = fs.directory(newPath)
try
fs.makeTree(directoryPath) unless fs.exists(directoryPath)
fs.move(oldPath, newPath)
dialog.close()
catch e
dialog.showError("Error: " + e.message + " Try a different path:")
@rootView.append(dialog)
removeSelectedEntry: ->
entry = @selectedEntry()
return unless entry
entryType = if entry instanceof DirectoryView then "directory" else "file"
atom.confirm(
"Are you sure you would like to delete the selected #{entryType}?",
"You are deleting #{entry.getPath()}",
"Move to Trash", (=> Native.moveToTrash(entry.getPath())),
"Cancel", null
"Delete", (=> fs.remove(entry.getPath()))
)
add: ->
selectedPath = @selectedEntry().getPath()
directoryPath = if fs.isFile(selectedPath) then fs.directory(selectedPath) else selectedPath
relativeDirectoryPath = @rootView.project.relativize(directoryPath)
relativeDirectoryPath += '/' if relativeDirectoryPath.length > 0
dialog = new Dialog
prompt: "Enter the path for the new file/directory. Directories end with '/':"
path: relativeDirectoryPath
select: false
onConfirm: (relativePath) =>
endsWithDirectorySeparator = /\/$/.test(relativePath)
path = @rootView.project.resolve(relativePath)
try
if fs.exists(path)
pathType = if fs.isFile(path) then "file" else "directory"
dialog.showError("Error: A #{pathType} already exists at path '#{path}'. Try a different path:")
else if endsWithDirectorySeparator
fs.makeTree(path)
dialog.cancel()
@entryForPath(path).buildEntries()
@selectEntryForPath(path)
else
fs.write(path, "")
@rootView.open(path)
dialog.close()
catch e
dialog.showError("Error: " + e.message + " Try a different path:")
@rootView.append(dialog)
selectedEntry: ->
@find('.selected')?.view()
selectEntry: (entry) ->
return false unless entry.get(0)
entry = entry.view() unless entry instanceof View
@selectedPath = entry.getPath()
@find('.selected').removeClass('selected')
entry.addClass('selected')
scrollToEntry: (entry) ->
displayElement = if (entry instanceof DirectoryView) then entry.header else entry
top = @scrollTop() + displayElement.position().top
bottom = top + displayElement.outerHeight()
if bottom > @scrollBottom()
@scrollBottom(bottom)
if top < @scrollTop()
@scrollTop(top)