pulsar/spec/project-spec.js

1374 lines
42 KiB
JavaScript
Raw Normal View History

const temp = require('temp').track()
const TextBuffer = require('text-buffer')
const Project = require('../src/project')
const fs = require('fs-plus')
const path = require('path')
2019-02-22 10:55:17 +03:00
const { Directory } = require('pathwatcher')
const { stopAllWatchers } = require('../src/path-watcher')
const GitRepository = require('../src/git-repository')
2017-10-16 03:48:37 +03:00
describe('Project', () => {
beforeEach(() => {
const directory = atom.project.getDirectories()[0]
const paths = directory ? [directory.resolve('dir')] : [null]
atom.project.setPaths(paths)
// Wait for project's service consumers to be asynchronously added
waits(1)
})
2017-10-16 03:48:37 +03:00
describe('serialization', () => {
let deserializedProject = null
let notQuittingProject = null
let quittingProject = null
2017-10-16 03:48:37 +03:00
afterEach(() => {
if (deserializedProject != null) {
deserializedProject.destroy()
}
if (notQuittingProject != null) {
notQuittingProject.destroy()
}
2017-10-19 03:10:24 +03:00
if (quittingProject != null) {
quittingProject.destroy()
}
})
2017-10-16 03:48:37 +03:00
it("does not deserialize paths to directories that don't exist", () => {
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
const state = atom.project.serialize()
state.paths.push('/directory/that/does/not/exist')
let err = null
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
deserializedProject.deserialize(state, atom.deserializers).catch(e => {
err = e
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths())
2019-02-22 10:55:17 +03:00
expect(err.missingProjectPaths).toEqual([
'/directory/that/does/not/exist'
])
})
})
2017-10-16 03:48:37 +03:00
it('does not deserialize paths that are now files', () => {
const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child')
fs.mkdirSync(childPath)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
atom.project.setPaths([childPath])
const state = atom.project.serialize()
fs.rmdirSync(childPath)
fs.writeFileSync(childPath, 'surprise!\n')
let err = null
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
deserializedProject.deserialize(state, atom.deserializers).catch(e => {
err = e
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(deserializedProject.getPaths()).toEqual([])
expect(err.missingProjectPaths).toEqual([childPath])
})
})
2017-10-16 03:48:37 +03:00
it('does not include unretained buffers in the serialized state', () => {
waitsForPromise(() => atom.project.bufferForPath('a'))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.getBuffers().length).toBe(1)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
runs(() => expect(deserializedProject.getBuffers().length).toBe(0))
})
2017-10-16 03:48:37 +03:00
it('listens for destroyed events on deserialized buffers and removes them when they are destroyed', () => {
waitsForPromise(() => atom.workspace.open('a'))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.getBuffers().length).toBe(1)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(deserializedProject.getBuffers().length).toBe(1)
deserializedProject.getBuffers()[0].destroy()
expect(deserializedProject.getBuffers().length).toBe(0)
})
})
2017-10-16 03:48:37 +03:00
it('does not deserialize buffers when their path is now a directory', () => {
2019-02-22 10:55:17 +03:00
const pathToOpen = path.join(
temp.mkdirSync('atom-spec-project'),
'file.txt'
)
waitsForPromise(() => atom.workspace.open(pathToOpen))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.getBuffers().length).toBe(1)
fs.mkdirSync(pathToOpen)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
runs(() => expect(deserializedProject.getBuffers().length).toBe(0))
})
2017-10-16 03:48:37 +03:00
it('does not deserialize buffers when their path is inaccessible', () => {
2019-02-22 10:55:17 +03:00
if (process.platform === 'win32') {
return
} // chmod not supported on win32
const pathToOpen = path.join(
temp.mkdirSync('atom-spec-project'),
'file.txt'
)
fs.writeFileSync(pathToOpen, '')
waitsForPromise(() => atom.workspace.open(pathToOpen))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.getBuffers().length).toBe(1)
fs.chmodSync(pathToOpen, '000')
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
runs(() => expect(deserializedProject.getBuffers().length).toBe(0))
})
2017-10-16 03:48:37 +03:00
it('does not deserialize buffers with their path is no longer present', () => {
2019-02-22 10:55:17 +03:00
const pathToOpen = path.join(
temp.mkdirSync('atom-spec-project'),
'file.txt'
)
fs.writeFileSync(pathToOpen, '')
waitsForPromise(() => atom.workspace.open(pathToOpen))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.getBuffers().length).toBe(1)
fs.unlinkSync(pathToOpen)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
runs(() => expect(deserializedProject.getBuffers().length).toBe(0))
})
2017-10-16 03:48:37 +03:00
it('deserializes buffers that have never been saved before', () => {
2019-02-22 10:55:17 +03:00
const pathToOpen = path.join(
temp.mkdirSync('atom-spec-project'),
'file.txt'
)
waitsForPromise(() => atom.workspace.open(pathToOpen))
2017-10-16 03:48:37 +03:00
runs(() => {
atom.workspace.getActiveTextEditor().setText('unsaved\n')
expect(atom.project.getBuffers().length).toBe(1)
deserializedProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
deserializedProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(deserializedProject.getBuffers().length).toBe(1)
expect(deserializedProject.getBuffers()[0].getPath()).toBe(pathToOpen)
expect(deserializedProject.getBuffers()[0].getText()).toBe('unsaved\n')
})
})
2017-10-16 03:48:37 +03:00
it('serializes marker layers and history only if Atom is quitting', () => {
waitsForPromise(() => atom.workspace.open('a'))
let bufferA = null
let layerA = null
let markerA = null
2017-10-16 03:48:37 +03:00
runs(() => {
bufferA = atom.project.getBuffers()[0]
2019-02-22 10:55:17 +03:00
layerA = bufferA.addMarkerLayer({ persistent: true })
markerA = layerA.markPosition([0, 3])
bufferA.append('!')
notQuittingProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
notQuittingProject.deserialize(
atom.project.serialize({ isUnloading: false })
)
)
2017-10-16 03:48:37 +03:00
runs(() => {
2019-02-22 10:55:17 +03:00
expect(
notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id),
x => x.getMarker(markerA.id)
).toBeUndefined()
expect(notQuittingProject.getBuffers()[0].undo()).toBe(false)
quittingProject = new Project({
notificationManager: atom.notifications,
packageManager: atom.packages,
confirm: atom.confirm,
grammarRegistry: atom.grammars
})
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
quittingProject.deserialize(
atom.project.serialize({ isUnloading: true })
)
)
2017-10-16 03:48:37 +03:00
runs(() => {
2019-02-22 10:55:17 +03:00
expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id), x =>
x.getMarker(markerA.id)
).not.toBeUndefined()
expect(quittingProject.getBuffers()[0].undo()).toBe(true)
})
})
})
2017-11-02 22:13:31 +03:00
describe('when an editor is saved and the project has no path', () => {
2017-10-16 03:48:37 +03:00
it("sets the project's path to the saved file's parent directory", () => {
const tempFile = temp.openSync().path
atom.project.setPaths([])
expect(atom.project.getPaths()[0]).toBeUndefined()
let editor = null
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open().then(o => {
editor = o
})
)
waitsForPromise(() => editor.saveAs(tempFile))
2019-02-22 10:55:17 +03:00
runs(() =>
expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile))
)
})
2017-11-02 22:13:31 +03:00
})
2018-03-06 22:06:22 +03:00
describe('.replace', () => {
let projectSpecification, projectPath1, projectPath2
beforeEach(() => {
2018-02-27 02:45:56 +03:00
atom.project.replace(null)
projectPath1 = temp.mkdirSync('project-path1')
projectPath2 = temp.mkdirSync('project-path2')
2018-03-06 22:06:22 +03:00
projectSpecification = {
paths: [projectPath1, projectPath2],
2018-03-06 22:09:58 +03:00
originPath: 'originPath',
config: {
2019-02-22 10:55:17 +03:00
baz: 'buzz'
}
}
})
2018-03-06 22:06:22 +03:00
it('sets a project specification', () => {
expect(atom.config.get('baz')).toBeUndefined()
2018-03-06 22:06:22 +03:00
atom.project.replace(projectSpecification)
expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2])
2018-03-02 06:45:40 +03:00
expect(atom.config.get('baz')).toBe('buzz')
})
2018-03-06 22:06:22 +03:00
it('clears a project through replace with no params', () => {
expect(atom.config.get('baz')).toBeUndefined()
2018-03-06 22:06:22 +03:00
atom.project.replace(projectSpecification)
2018-03-06 22:09:58 +03:00
expect(atom.config.get('baz')).toBe('buzz')
expect(atom.project.getPaths()).toEqual([projectPath1, projectPath2])
2018-02-27 02:45:56 +03:00
atom.project.replace()
expect(atom.config.get('baz')).toBeUndefined()
expect(atom.project.getPaths()).toEqual([])
})
2018-03-06 22:06:22 +03:00
it('responds to change of project specification', () => {
let wasCalled = false
const callback = () => {
wasCalled = true
}
2018-02-27 02:45:56 +03:00
atom.project.onDidReplace(callback)
2018-03-06 22:06:22 +03:00
atom.project.replace(projectSpecification)
expect(wasCalled).toBe(true)
wasCalled = false
2018-02-27 02:45:56 +03:00
atom.project.replace()
expect(wasCalled).toBe(true)
})
})
2017-10-16 03:48:37 +03:00
describe('before and after saving a buffer', () => {
2017-10-16 03:25:10 +03:00
let buffer
beforeEach(() =>
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.bufferForPath(path.join(__dirname, 'fixtures', 'sample.js'))
.then(o => {
buffer = o
buffer.retain()
})
)
)
afterEach(() => buffer.release())
2017-10-16 03:48:37 +03:00
it('emits save events on the main process', () => {
spyOn(atom.project.applicationDelegate, 'emitDidSavePath')
spyOn(atom.project.applicationDelegate, 'emitWillSavePath')
waitsForPromise(() => buffer.save())
2017-10-16 03:48:37 +03:00
runs(() => {
2019-02-22 10:55:17 +03:00
expect(
atom.project.applicationDelegate.emitDidSavePath.calls.length
).toBe(1)
expect(
atom.project.applicationDelegate.emitDidSavePath
).toHaveBeenCalledWith(buffer.getPath())
expect(
atom.project.applicationDelegate.emitWillSavePath.calls.length
).toBe(1)
expect(
atom.project.applicationDelegate.emitWillSavePath
).toHaveBeenCalledWith(buffer.getPath())
})
})
})
2017-10-16 03:48:37 +03:00
describe('when a watch error is thrown from the TextBuffer', () => {
let editor = null
beforeEach(() =>
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open(require.resolve('./fixtures/dir/a')).then(o => {
editor = o
})
)
)
2017-10-16 03:48:37 +03:00
it('creates a warning notification', () => {
let noteSpy
2019-02-22 10:55:17 +03:00
atom.notifications.onDidAddNotification((noteSpy = jasmine.createSpy()))
const error = new Error('SomeError')
error.eventType = 'resurrect'
editor.buffer.emitter.emit('will-throw-watch-error', {
handle: jasmine.createSpy(),
error
2019-02-22 10:55:17 +03:00
})
expect(noteSpy).toHaveBeenCalled()
const notification = noteSpy.mostRecentCall.args[0]
expect(notification.getType()).toBe('warning')
expect(notification.getDetail()).toBe('SomeError')
expect(notification.getMessage()).toContain('`resurrect`')
2019-02-22 10:55:17 +03:00
expect(notification.getMessage()).toContain(
path.join('fixtures', 'dir', 'a')
)
})
})
2017-10-16 03:48:37 +03:00
describe('when a custom repository-provider service is provided', () => {
2017-10-16 03:25:10 +03:00
let fakeRepositoryProvider, fakeRepository
2017-10-16 03:48:37 +03:00
beforeEach(() => {
2019-02-22 10:55:17 +03:00
fakeRepository = {
destroy () {
return null
}
}
fakeRepositoryProvider = {
2019-02-22 10:55:17 +03:00
repositoryForDirectory (directory) {
return Promise.resolve(fakeRepository)
},
repositoryForDirectorySync (directory) {
return fakeRepository
}
}
})
2017-10-16 03:48:37 +03:00
it('uses it to create repositories for any directories that need one', () => {
const projectPath = temp.mkdirSync('atom-project')
atom.project.setPaths([projectPath])
expect(atom.project.getRepositories()).toEqual([null])
2019-02-22 10:55:17 +03:00
atom.packages.serviceHub.provide(
'atom.repository-provider',
'0.1.0',
fakeRepositoryProvider
)
waitsFor(() => atom.project.repositoryProviders.length > 1)
runs(() => atom.project.getRepositories()[0] === fakeRepository)
})
2017-10-16 03:48:37 +03:00
it('does not create any new repositories if every directory has a repository', () => {
const repositories = atom.project.getRepositories()
expect(repositories.length).toEqual(1)
expect(repositories[0]).toBeTruthy()
2019-02-22 10:55:17 +03:00
atom.packages.serviceHub.provide(
'atom.repository-provider',
'0.1.0',
fakeRepositoryProvider
)
waitsFor(() => atom.project.repositoryProviders.length > 1)
runs(() => expect(atom.project.getRepositories()).toBe(repositories))
})
2017-10-16 03:48:37 +03:00
it('stops using it to create repositories when the service is removed', () => {
atom.project.setPaths([])
2019-02-22 10:55:17 +03:00
const disposable = atom.packages.serviceHub.provide(
'atom.repository-provider',
'0.1.0',
fakeRepositoryProvider
)
waitsFor(() => atom.project.repositoryProviders.length > 1)
2017-10-16 03:48:37 +03:00
runs(() => {
disposable.dispose()
atom.project.addPath(temp.mkdirSync('atom-project'))
expect(atom.project.getRepositories()).toEqual([null])
})
})
})
2017-10-16 03:48:37 +03:00
describe('when a custom directory-provider service is provided', () => {
class DummyDirectory {
2017-10-19 03:10:24 +03:00
constructor (aPath) {
this.path = aPath
}
2019-02-22 10:55:17 +03:00
getPath () {
return this.path
}
getFile () {
return {
existsSync () {
return false
}
}
}
getSubdirectory () {
return {
existsSync () {
return false
}
}
}
isRoot () {
return true
}
existsSync () {
return this.path.endsWith('does-exist')
}
contains (filePath) {
return filePath.startsWith(this.path)
}
onDidChangeFiles (callback) {
onDidChangeFilesCallback = callback
2019-02-22 10:55:17 +03:00
return { dispose: () => {} }
}
}
let serviceDisposable = null
let onDidChangeFilesCallback = null
2017-10-16 03:48:37 +03:00
beforeEach(() => {
2019-02-22 10:55:17 +03:00
serviceDisposable = atom.packages.serviceHub.provide(
'atom.directory-provider',
'0.1.0',
{
directoryForURISync (uri) {
if (uri.startsWith('ssh://')) {
return new DummyDirectory(uri)
} else {
return null
}
}
}
2019-02-22 10:55:17 +03:00
)
onDidChangeFilesCallback = null
waitsFor(() => atom.project.directoryProviders.length > 0)
})
2017-10-16 03:48:37 +03:00
it("uses the provider's custom directories for any paths that it handles", () => {
const localPath = temp.mkdirSync('local-path')
const remotePath = 'ssh://foreign-directory:8080/does-exist'
atom.project.setPaths([localPath, remotePath])
let directories = atom.project.getDirectories()
expect(directories[0].getPath()).toBe(localPath)
expect(directories[0] instanceof Directory).toBe(true)
expect(directories[1].getPath()).toBe(remotePath)
expect(directories[1] instanceof DummyDirectory).toBe(true)
// It does not add new remote paths that do not exist
2019-02-22 10:55:17 +03:00
const nonExistentRemotePath =
'ssh://another-directory:8080/does-not-exist'
atom.project.addPath(nonExistentRemotePath)
expect(atom.project.getDirectories().length).toBe(2)
// It adds new remote paths if their directories exist.
const newRemotePath = 'ssh://another-directory:8080/does-exist'
atom.project.addPath(newRemotePath)
directories = atom.project.getDirectories()
expect(directories[2].getPath()).toBe(newRemotePath)
expect(directories[2] instanceof DummyDirectory).toBe(true)
})
2017-10-16 03:48:37 +03:00
it('stops using the provider when the service is removed', () => {
serviceDisposable.dispose()
atom.project.setPaths(['ssh://foreign-directory:8080/does-exist'])
expect(atom.project.getDirectories().length).toBe(0)
})
it('uses the custom onDidChangeFiles as the watcher if available', () => {
// Ensure that all preexisting watchers are stopped
waitsForPromise(() => stopAllWatchers())
const remotePath = 'ssh://another-directory:8080/does-exist'
runs(() => atom.project.setPaths([remotePath]))
waitsForPromise(() => atom.project.getWatcherPromise(remotePath))
runs(() => {
expect(onDidChangeFilesCallback).not.toBeNull()
const changeSpy = jasmine.createSpy('atom.project.onDidChangeFiles')
const disposable = atom.project.onDidChangeFiles(changeSpy)
2019-02-22 10:55:17 +03:00
const events = [{ action: 'created', path: remotePath + '/test.txt' }]
onDidChangeFilesCallback(events)
expect(changeSpy).toHaveBeenCalledWith(events)
disposable.dispose()
})
})
})
2017-10-16 03:48:37 +03:00
describe('.open(path)', () => {
2017-10-16 03:25:10 +03:00
let absolutePath, newBufferHandler
2017-10-16 03:48:37 +03:00
beforeEach(() => {
absolutePath = require.resolve('./fixtures/dir/a')
newBufferHandler = jasmine.createSpy('newBufferHandler')
atom.project.onDidAddBuffer(newBufferHandler)
})
2017-11-02 22:13:31 +03:00
describe("when given an absolute path that isn't currently open", () => {
2017-10-16 03:48:37 +03:00
it("returns a new edit session for the given path and emits 'buffer-created'", () => {
let editor = null
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open(absolutePath).then(o => {
editor = o
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(editor.buffer.getPath()).toBe(absolutePath)
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
})
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe("when given a relative path that isn't currently opened", () => {
2017-10-16 03:48:37 +03:00
it("returns a new edit session for the given path (relative to the project root) and emits 'buffer-created'", () => {
let editor = null
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open(absolutePath).then(o => {
editor = o
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(editor.buffer.getPath()).toBe(absolutePath)
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
})
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('when passed the path to a buffer that is currently opened', () => {
2017-10-16 03:48:37 +03:00
it('returns a new edit session containing currently opened buffer', () => {
let editor = null
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open(absolutePath).then(o => {
editor = o
})
)
runs(() => newBufferHandler.reset())
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.workspace
.open(absolutePath)
.then(({ buffer }) => expect(buffer).toBe(editor.buffer))
)
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.workspace.open('a').then(({ buffer }) => {
expect(buffer).toBe(editor.buffer)
expect(newBufferHandler).not.toHaveBeenCalled()
})
)
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('when not passed a path', () => {
2017-10-16 03:48:37 +03:00
it("returns a new edit session and emits 'buffer-created'", () => {
let editor = null
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.workspace.open().then(o => {
editor = o
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(editor.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
})
})
2017-11-02 22:13:31 +03:00
})
})
2017-10-16 03:48:37 +03:00
describe('.bufferForPath(path)', () => {
let buffer = null
beforeEach(() =>
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project.bufferForPath('a').then(o => {
buffer = o
buffer.retain()
})
)
)
afterEach(() => buffer.release())
2017-10-16 03:48:37 +03:00
describe('when opening a previously opened path', () => {
it('does not create a new buffer', () => {
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.bufferForPath('a')
.then(anotherBuffer => expect(anotherBuffer).toBe(buffer))
)
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.bufferForPath('b')
.then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer))
)
waitsForPromise(() =>
Promise.all([
atom.project.bufferForPath('c'),
atom.project.bufferForPath('c')
2017-10-16 03:48:37 +03:00
]).then(([buffer1, buffer2]) => {
expect(buffer1).toBe(buffer2)
})
)
})
2017-10-16 03:48:37 +03:00
it('retries loading the buffer if it previously failed', () => {
2019-02-22 10:55:17 +03:00
waitsForPromise({ shouldReject: true }, () => {
spyOn(TextBuffer, 'load').andCallFake(() =>
Promise.reject(new Error('Could not open file'))
)
return atom.project.bufferForPath('b')
})
2019-02-22 10:55:17 +03:00
waitsForPromise({ shouldReject: false }, () => {
TextBuffer.load.andCallThrough()
return atom.project.bufferForPath('b')
})
})
2017-10-16 03:48:37 +03:00
it('creates a new buffer if the previous buffer was destroyed', () => {
buffer.release()
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.bufferForPath('b')
.then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer))
)
})
})
})
2017-10-16 03:48:37 +03:00
describe('.repositoryForDirectory(directory)', () => {
2017-11-02 22:13:31 +03:00
it('resolves to null when the directory does not have a repository', () => {
2017-10-16 03:48:37 +03:00
waitsForPromise(() => {
const directory = new Directory('/tmp')
2019-02-22 10:55:17 +03:00
return atom.project.repositoryForDirectory(directory).then(result => {
expect(result).toBeNull()
expect(atom.project.repositoryProviders.length).toBeGreaterThan(0)
expect(atom.project.repositoryPromisesByPath.size).toBe(0)
})
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
it('resolves to a GitRepository and is cached when the given directory is a Git repo', () => {
2017-10-16 03:48:37 +03:00
waitsForPromise(() => {
const directory = new Directory(path.join(__dirname, '..'))
const promise = atom.project.repositoryForDirectory(directory)
2019-02-22 10:55:17 +03:00
return promise.then(result => {
expect(result).toBeInstanceOf(GitRepository)
const dirPath = directory.getRealPathSync()
expect(result.getPath()).toBe(path.join(dirPath, '.git'))
// Verify that the result is cached.
expect(atom.project.repositoryForDirectory(directory)).toBe(promise)
})
})
2017-11-02 22:13:31 +03:00
})
2017-10-16 03:48:37 +03:00
it('creates a new repository if a previous one with the same directory had been destroyed', () => {
let repository = null
const directory = new Directory(path.join(__dirname, '..'))
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.project.repositoryForDirectory(directory).then(repo => {
repository = repo
})
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(repository.isDestroyed()).toBe(false)
repository.destroy()
expect(repository.isDestroyed()).toBe(true)
})
2019-02-22 10:55:17 +03:00
waitsForPromise(() =>
atom.project.repositoryForDirectory(directory).then(repo => {
repository = repo
})
)
runs(() => expect(repository.isDestroyed()).toBe(false))
})
})
2017-10-16 03:48:37 +03:00
describe('.setPaths(paths, options)', () => {
2017-11-02 22:13:31 +03:00
describe('when path is a file', () => {
2017-10-16 03:48:37 +03:00
it("sets its path to the file's parent directory and updates the root directory", () => {
const filePath = require.resolve('./fixtures/dir/a')
atom.project.setPaths([filePath])
expect(atom.project.getPaths()[0]).toEqual(path.dirname(filePath))
2019-02-22 10:55:17 +03:00
expect(atom.project.getDirectories()[0].path).toEqual(
path.dirname(filePath)
)
})
2017-11-02 22:13:31 +03:00
})
2017-10-16 03:48:37 +03:00
describe('when path is a directory', () => {
it('assigns the directories and repositories', () => {
const directory1 = temp.mkdirSync('non-git-repo')
const directory2 = temp.mkdirSync('git-repo1')
const directory3 = temp.mkdirSync('git-repo2')
2019-02-22 10:55:17 +03:00
const gitDirPath = fs.absolute(
path.join(__dirname, 'fixtures', 'git', 'master.git')
)
fs.copySync(gitDirPath, path.join(directory2, '.git'))
fs.copySync(gitDirPath, path.join(directory3, '.git'))
atom.project.setPaths([directory1, directory2, directory3])
2017-10-16 03:25:10 +03:00
const [repo1, repo2, repo3] = atom.project.getRepositories()
expect(repo1).toBeNull()
expect(repo2.getShortHead()).toBe('master')
2019-02-22 10:55:17 +03:00
expect(repo2.getPath()).toBe(
fs.realpathSync(path.join(directory2, '.git'))
)
expect(repo3.getShortHead()).toBe('master')
2019-02-22 10:55:17 +03:00
expect(repo3.getPath()).toBe(
fs.realpathSync(path.join(directory3, '.git'))
)
})
2017-10-16 03:48:37 +03:00
it('calls callbacks registered with ::onDidChangePaths', () => {
const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
atom.project.onDidChangePaths(onDidChangePathsSpy)
2019-02-22 10:55:17 +03:00
const paths = [temp.mkdirSync('dir1'), temp.mkdirSync('dir2')]
atom.project.setPaths(paths)
expect(onDidChangePathsSpy.callCount).toBe(1)
expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths)
})
2017-10-16 03:48:37 +03:00
it('optionally throws an error with any paths that did not exist', () => {
2019-02-22 10:55:17 +03:00
const paths = [
temp.mkdirSync('exists0'),
'/doesnt-exists/0',
temp.mkdirSync('exists1'),
'/doesnt-exists/1'
]
try {
2019-02-22 10:55:17 +03:00
atom.project.setPaths(paths, { mustExist: true })
expect('no exception thrown').toBeUndefined()
} catch (e) {
expect(e.missingProjectPaths).toEqual([paths[1], paths[3]])
}
expect(atom.project.getPaths()).toEqual([paths[0], paths[2]])
})
})
2017-11-02 22:13:31 +03:00
describe('when no paths are given', () => {
2017-10-16 03:48:37 +03:00
it('clears its path', () => {
atom.project.setPaths([])
expect(atom.project.getPaths()).toEqual([])
expect(atom.project.getDirectories()).toEqual([])
})
2017-11-02 22:13:31 +03:00
})
2017-10-16 03:48:37 +03:00
it('normalizes the path to remove consecutive slashes, ., and .. segments', () => {
2019-02-22 10:55:17 +03:00
atom.project.setPaths([
`${require.resolve('./fixtures/dir/a')}${path.sep}b${path.sep}${
path.sep
}..`
])
expect(atom.project.getPaths()[0]).toEqual(
path.dirname(require.resolve('./fixtures/dir/a'))
)
expect(atom.project.getDirectories()[0].path).toEqual(
path.dirname(require.resolve('./fixtures/dir/a'))
)
})
})
2017-10-16 03:48:37 +03:00
describe('.addPath(path, options)', () => {
it('calls callbacks registered with ::onDidChangePaths', () => {
const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
atom.project.onDidChangePaths(onDidChangePathsSpy)
2017-10-16 03:25:10 +03:00
const [oldPath] = atom.project.getPaths()
const newPath = temp.mkdirSync('dir')
atom.project.addPath(newPath)
expect(onDidChangePathsSpy.callCount).toBe(1)
2019-02-22 10:55:17 +03:00
expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([
oldPath,
newPath
])
})
2017-10-16 03:48:37 +03:00
it("doesn't add redundant paths", () => {
const onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
atom.project.onDidChangePaths(onDidChangePathsSpy)
2017-10-16 03:25:10 +03:00
const [oldPath] = atom.project.getPaths()
// Doesn't re-add an existing root directory
atom.project.addPath(oldPath)
expect(atom.project.getPaths()).toEqual([oldPath])
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
// Doesn't add an entry for a file-path within an existing root directory
atom.project.addPath(path.join(oldPath, 'some-file.txt'))
expect(atom.project.getPaths()).toEqual([oldPath])
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
// Does add an entry for a directory within an existing directory
const newPath = path.join(oldPath, 'a-dir')
atom.project.addPath(newPath)
expect(atom.project.getPaths()).toEqual([oldPath, newPath])
expect(onDidChangePathsSpy).toHaveBeenCalled()
})
2017-10-16 03:48:37 +03:00
it("doesn't add non-existent directories", () => {
const previousPaths = atom.project.getPaths()
atom.project.addPath('/this-definitely/does-not-exist')
expect(atom.project.getPaths()).toEqual(previousPaths)
})
2017-11-02 22:13:31 +03:00
it('optionally throws on non-existent directories', () => {
2019-02-22 10:55:17 +03:00
expect(() =>
atom.project.addPath('/this-definitely/does-not-exist', {
mustExist: true
})
).toThrow()
2017-11-02 22:13:31 +03:00
})
})
2017-10-16 03:48:37 +03:00
describe('.removePath(path)', () => {
let onDidChangePathsSpy = null
2017-10-16 03:48:37 +03:00
beforeEach(() => {
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths listener')
atom.project.onDidChangePaths(onDidChangePathsSpy)
})
2017-10-16 03:48:37 +03:00
it('removes the directory and repository for the path', () => {
const result = atom.project.removePath(atom.project.getPaths()[0])
expect(atom.project.getDirectories()).toEqual([])
expect(atom.project.getRepositories()).toEqual([])
expect(atom.project.getPaths()).toEqual([])
expect(result).toBe(true)
expect(onDidChangePathsSpy).toHaveBeenCalled()
})
2017-10-16 03:48:37 +03:00
it("does nothing if the path is not one of the project's root paths", () => {
const originalPaths = atom.project.getPaths()
const result = atom.project.removePath(originalPaths[0] + 'xyz')
expect(result).toBe(false)
expect(atom.project.getPaths()).toEqual(originalPaths)
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
})
2017-10-16 03:48:37 +03:00
it("doesn't destroy the repository if it is shared by another root directory", () => {
atom.project.setPaths([__dirname, path.join(__dirname, '..', 'src')])
atom.project.removePath(__dirname)
2019-02-22 10:55:17 +03:00
expect(atom.project.getPaths()).toEqual([
path.join(__dirname, '..', 'src')
])
expect(atom.project.getRepositories()[0].isSubmodule('src')).toBe(false)
})
2017-10-16 03:48:37 +03:00
it('removes a path that is represented as a URI', () => {
atom.packages.serviceHub.provide('atom.directory-provider', '0.1.0', {
directoryForURISync (uri) {
return {
2019-02-22 10:55:17 +03:00
getPath () {
return uri
},
getSubdirectory () {
return {}
},
isRoot () {
return true
},
existsSync () {
return true
},
off () {}
}
}
})
const ftpURI = 'ftp://example.com/some/folder'
atom.project.setPaths([ftpURI])
expect(atom.project.getPaths()).toEqual([ftpURI])
atom.project.removePath(ftpURI)
expect(atom.project.getPaths()).toEqual([])
})
})
2017-10-16 03:48:37 +03:00
describe('.onDidChangeFiles()', () => {
let sub = []
const events = []
2017-10-16 03:48:37 +03:00
let checkCallback = () => {}
beforeEach(() => {
2019-02-22 10:55:17 +03:00
sub = atom.project.onDidChangeFiles(incoming => {
2017-10-19 03:10:24 +03:00
events.push(...incoming)
checkCallback()
})
})
afterEach(() => sub.dispose())
2019-02-22 10:55:17 +03:00
const waitForEvents = paths => {
const remaining = new Set(paths.map(p => fs.realpathSync(p)))
2017-10-16 03:48:37 +03:00
return new Promise((resolve, reject) => {
checkCallback = () => {
2019-02-22 10:55:17 +03:00
for (let event of events) {
remaining.delete(event.path)
}
if (remaining.size === 0) {
resolve()
}
}
2017-10-16 03:48:37 +03:00
const expire = () => {
checkCallback = () => {}
2017-10-16 03:25:10 +03:00
console.error('Paths not seen:', remaining)
2019-02-22 10:55:17 +03:00
reject(
new Error('Expired before all expected events were delivered.')
)
}
checkCallback()
setTimeout(expire, 2000)
})
}
2017-10-16 03:48:37 +03:00
it('reports filesystem changes within project paths', () => {
const dirOne = temp.mkdirSync('atom-spec-project-one')
const fileOne = path.join(dirOne, 'file-one.txt')
const fileTwo = path.join(dirOne, 'file-two.txt')
const dirTwo = temp.mkdirSync('atom-spec-project-two')
const fileThree = path.join(dirTwo, 'file-three.txt')
// Ensure that all preexisting watchers are stopped
waitsForPromise(() => stopAllWatchers())
runs(() => atom.project.setPaths([dirOne]))
waitsForPromise(() => atom.project.getWatcherPromise(dirOne))
2017-10-16 03:48:37 +03:00
runs(() => {
expect(atom.project.watcherPromisesByPath[dirTwo]).toEqual(undefined)
fs.writeFileSync(fileThree, 'three\n')
fs.writeFileSync(fileTwo, 'two\n')
fs.writeFileSync(fileOne, 'one\n')
})
waitsForPromise(() => waitForEvents([fileOne, fileTwo]))
2019-02-22 10:55:17 +03:00
runs(() =>
expect(events.some(event => event.path === fileThree)).toBeFalsy()
)
})
})
2017-11-02 22:13:31 +03:00
describe('.onDidAddBuffer()', () => {
2017-10-16 03:48:37 +03:00
it('invokes the callback with added text buffers', () => {
const buffers = []
const added = []
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.buildBuffer(require.resolve('./fixtures/dir/a'))
.then(o => buffers.push(o))
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(buffers.length).toBe(1)
atom.project.onDidAddBuffer(buffer => added.push(buffer))
})
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.buildBuffer(require.resolve('./fixtures/dir/b'))
.then(o => buffers.push(o))
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(buffers.length).toBe(2)
expect(added).toEqual([buffers[1]])
})
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('.observeBuffers()', () => {
2017-10-16 03:48:37 +03:00
it('invokes the observer with current and future text buffers', () => {
const buffers = []
const observed = []
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.buildBuffer(require.resolve('./fixtures/dir/a'))
.then(o => buffers.push(o))
)
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.buildBuffer(require.resolve('./fixtures/dir/b'))
.then(o => buffers.push(o))
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(buffers.length).toBe(2)
atom.project.observeBuffers(buffer => observed.push(buffer))
expect(observed).toEqual(buffers)
})
waitsForPromise(() =>
2019-02-22 10:55:17 +03:00
atom.project
.buildBuffer(require.resolve('./fixtures/dir/b'))
.then(o => buffers.push(o))
)
2017-10-16 03:48:37 +03:00
runs(() => {
expect(observed.length).toBe(3)
expect(buffers.length).toBe(3)
expect(observed).toEqual(buffers)
})
})
2017-11-02 22:13:31 +03:00
})
describe('.observeRepositories()', () => {
it('invokes the observer with current and future repositories', () => {
const observed = []
const directory1 = temp.mkdirSync('git-repo1')
2019-02-22 10:55:17 +03:00
const gitDirPath1 = fs.absolute(
path.join(__dirname, 'fixtures', 'git', 'master.git')
)
fs.copySync(gitDirPath1, path.join(directory1, '.git'))
const directory2 = temp.mkdirSync('git-repo2')
2019-02-22 10:55:17 +03:00
const gitDirPath2 = fs.absolute(
path.join(
__dirname,
'fixtures',
'git',
'repo-with-submodules',
'git.git'
)
)
fs.copySync(gitDirPath2, path.join(directory2, '.git'))
atom.project.setPaths([directory1])
2019-02-22 10:55:17 +03:00
const disposable = atom.project.observeRepositories(repo =>
observed.push(repo)
)
expect(observed.length).toBe(1)
2019-02-22 10:55:17 +03:00
expect(observed[0].getReferenceTarget('refs/heads/master')).toBe(
'ef046e9eecaa5255ea5e9817132d4001724d6ae1'
)
atom.project.addPath(directory2)
expect(observed.length).toBe(2)
2019-02-22 10:55:17 +03:00
expect(observed[1].getReferenceTarget('refs/heads/master')).toBe(
'd2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5'
)
disposable.dispose()
})
})
describe('.onDidAddRepository()', () => {
it('invokes callback when a path is added and the path is the root of a repository', () => {
const observed = []
2019-02-22 10:55:17 +03:00
const disposable = atom.project.onDidAddRepository(repo =>
observed.push(repo)
)
const projectRootPath = temp.mkdirSync()
2019-02-22 10:55:17 +03:00
const fixtureRepoPath = fs.absolute(
path.join(__dirname, 'fixtures', 'git', 'master.git')
)
fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git'))
atom.project.addPath(projectRootPath)
expect(observed.length).toBe(1)
2019-02-22 10:55:17 +03:00
expect(observed[0].getOriginURL()).toEqual(
'https://github.com/example-user/example-repo.git'
)
disposable.dispose()
})
it('invokes callback when a path is added and the path is subdirectory of a repository', () => {
const observed = []
2019-02-22 10:55:17 +03:00
const disposable = atom.project.onDidAddRepository(repo =>
observed.push(repo)
)
const projectRootPath = temp.mkdirSync()
2019-02-22 10:55:17 +03:00
const fixtureRepoPath = fs.absolute(
path.join(__dirname, 'fixtures', 'git', 'master.git')
)
fs.copySync(fixtureRepoPath, path.join(projectRootPath, '.git'))
const projectSubDirPath = path.join(projectRootPath, 'sub-dir')
fs.mkdirSync(projectSubDirPath)
atom.project.addPath(projectSubDirPath)
expect(observed.length).toBe(1)
2019-02-22 10:55:17 +03:00
expect(observed[0].getOriginURL()).toEqual(
'https://github.com/example-user/example-repo.git'
)
disposable.dispose()
})
it('does not invoke callback when a path is added and the path is not part of a repository', () => {
const observed = []
2019-02-22 10:55:17 +03:00
const disposable = atom.project.onDidAddRepository(repo =>
observed.push(repo)
)
atom.project.addPath(temp.mkdirSync('not-a-repository'))
expect(observed.length).toBe(0)
disposable.dispose()
})
})
2017-10-16 03:48:37 +03:00
describe('.relativize(path)', () => {
it('returns the path, relative to whichever root directory it is inside of', () => {
atom.project.addPath(temp.mkdirSync('another-path'))
let rootPath = atom.project.getPaths()[0]
let childPath = path.join(rootPath, 'some', 'child', 'directory')
2019-02-22 10:55:17 +03:00
expect(atom.project.relativize(childPath)).toBe(
path.join('some', 'child', 'directory')
)
rootPath = atom.project.getPaths()[1]
childPath = path.join(rootPath, 'some', 'child', 'directory')
2019-02-22 10:55:17 +03:00
expect(atom.project.relativize(childPath)).toBe(
path.join('some', 'child', 'directory')
)
})
2017-10-16 03:48:37 +03:00
it('returns the given path if it is not in any of the root directories', () => {
const randomPath = path.join('some', 'random', 'path')
expect(atom.project.relativize(randomPath)).toBe(randomPath)
})
})
2017-10-16 03:48:37 +03:00
describe('.relativizePath(path)', () => {
it('returns the root path that contains the given path, and the path relativized to that root path', () => {
atom.project.addPath(temp.mkdirSync('another-path'))
let rootPath = atom.project.getPaths()[0]
let childPath = path.join(rootPath, 'some', 'child', 'directory')
2019-02-22 10:55:17 +03:00
expect(atom.project.relativizePath(childPath)).toEqual([
rootPath,
path.join('some', 'child', 'directory')
])
rootPath = atom.project.getPaths()[1]
childPath = path.join(rootPath, 'some', 'child', 'directory')
2019-02-22 10:55:17 +03:00
expect(atom.project.relativizePath(childPath)).toEqual([
rootPath,
path.join('some', 'child', 'directory')
])
})
2017-11-02 22:13:31 +03:00
describe("when the given path isn't inside of any of the project's path", () => {
2017-10-16 03:48:37 +03:00
it('returns null for the root path, and the given path unchanged', () => {
const randomPath = path.join('some', 'random', 'path')
2019-02-22 10:55:17 +03:00
expect(atom.project.relativizePath(randomPath)).toEqual([
null,
randomPath
])
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('when the given path is a URL', () => {
2017-10-16 03:48:37 +03:00
it('returns null for the root path, and the given path unchanged', () => {
const url = 'http://the-path'
expect(atom.project.relativizePath(url)).toEqual([null, url])
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('when the given path is inside more than one root folder', () => {
2017-10-16 03:48:37 +03:00
it('uses the root folder that is closest to the given path', () => {
atom.project.addPath(path.join(atom.project.getPaths()[0], 'a-dir'))
2019-02-22 10:55:17 +03:00
const inputPath = path.join(
atom.project.getPaths()[1],
'somewhere/something.txt'
)
expect(atom.project.getDirectories()[0].contains(inputPath)).toBe(true)
expect(atom.project.getDirectories()[1].contains(inputPath)).toBe(true)
expect(atom.project.relativizePath(inputPath)).toEqual([
atom.project.getPaths()[1],
path.join('somewhere', 'something.txt')
])
})
2017-11-02 22:13:31 +03:00
})
})
2017-11-02 22:13:31 +03:00
describe('.contains(path)', () => {
2017-10-16 03:48:37 +03:00
it('returns whether or not the given path is in one of the root directories', () => {
const rootPath = atom.project.getPaths()[0]
const childPath = path.join(rootPath, 'some', 'child', 'directory')
expect(atom.project.contains(childPath)).toBe(true)
const randomPath = path.join('some', 'random', 'path')
expect(atom.project.contains(randomPath)).toBe(false)
})
2017-11-02 22:13:31 +03:00
})
2017-11-02 22:13:31 +03:00
describe('.resolvePath(uri)', () => {
2017-10-19 03:10:24 +03:00
it('normalizes disk drive letter in passed path on #win32', () => {
expect(atom.project.resolvePath('d:\\file.txt')).toEqual('D:\\file.txt')
})
2017-11-02 22:13:31 +03:00
})
})