Skip to content

Commit

Permalink
feat: provide more stubs (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfbecker committed May 1, 2019
1 parent 2d74d6f commit 01b12ab
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 141 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
43 changes: 19 additions & 24 deletions src/index.test.ts → src/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'assert'
import * as sinon from 'sinon'
import { createStubExtensionContext, createStubSourcegraphAPI } from '.'
import * as sourcegraph from 'sourcegraph'
import { createStubSourcegraphAPI } from './api'

describe('createStubSourcegraphAPI()', () => {
it('should return a stub API', () => {
Expand All @@ -14,6 +15,13 @@ describe('createStubSourcegraphAPI()', () => {
assert.deepStrictEqual(stub.app.createDecorationType(), { key: 'decorationType1' })
assert.deepStrictEqual(stub.app.createDecorationType(), { key: 'decorationType2' })
})
it('should support managing the active Window', () => {
const stub = createStubSourcegraphAPI()
const newWindow = {} as sourcegraph.Window
assert.strictEqual(stub.app.activeWindow, undefined)
stub.app.activeWindowChanges.next(newWindow)
assert.strictEqual(stub.app.activeWindow, newWindow)
})
})
describe('configuration', () => {
it('should support reading configuration', async () => {
Expand All @@ -30,28 +38,15 @@ describe('createStubSourcegraphAPI()', () => {
assert.deepStrictEqual(listener.args[1][0], undefined)
})
})
// describe('languages', () => {
// // TODO fix Position/Range classes (publish as package) and MarkupKind enum assignability
// it('should allow registering a hover provider', () => {
// const stub = createStubSourcegraphAPI()
// stub.languages.registerHoverProvider(['*.ts'], {
// provideHover: (doc, pos) => ({
// contents: { kind: stub.MarkupKind.Markdown, value: doc.uri },
// range: new stub.Range(new stub.Position(1, 2), new stubs.Position(3, 4)),
// }),
// })
// })
// })
})

describe('createStubExtensionContext()', () => {
it('should create an extension context', () => {
const ctx = createStubExtensionContext()
const fn = sinon.spy()
ctx.subscriptions.add(fn)
sinon.assert.notCalled(fn)
sinon.assert.calledOnce(ctx.subscriptions.add)
ctx.subscriptions.unsubscribe()
sinon.assert.calledOnce(fn)
describe('languages', () => {
it('should allow registering a hover provider', () => {
const stub = createStubSourcegraphAPI()
stub.languages.registerHoverProvider(['*.ts'], {
provideHover: (doc, pos) => ({
contents: { kind: stub.MarkupKind.Markdown, value: `${doc.uri}:${pos.line}:${pos.character}` },
range: new stub.Range(new stub.Position(1, 2), new stub.Position(3, 4)),
}),
})
})
})
})
130 changes: 130 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Location, MarkupKind, NotificationType, Position, Range, Selection } from '@sourcegraph/extension-api-classes'
import { BehaviorSubject, Subject, Subscription } from 'rxjs'
import { mapTo } from 'rxjs/operators'
import * as sinon from 'sinon'
import * as sourcegraph from 'sourcegraph'
import { deprecate } from 'util'
import { assertTypeIsCompatible, notImplemented } from './util'

interface DeprecatedTypeDefinitionProvider {
provideTypeDefinition(
document: sourcegraph.TextDocument,
position: Position
): sourcegraph.ProviderResult<sourcegraph.Definition>
}

interface DeprecatedImplementationProvider {
provideImplementation(
document: sourcegraph.TextDocument,
position: Position
): sourcegraph.ProviderResult<sourcegraph.Definition>
}

let decorationTypeCounter = 0

/**
* Creates an object that (mostly) implements the Sourcegraph API,
* with all methods being Sinon spys and all Subscribables being Subjects.
*/
export const createStubSourcegraphAPI = () => {
const configSubject = new BehaviorSubject<any>({})
const rootChanges = new Subject<void>()
const openedTextDocuments = new Subject<sourcegraph.TextDocument>()
const stubs /*: typeof import('sourcegraph') */ = {
// Classes
URI: URL,
Position,
Range,
Location,
Selection,

// Enums
MarkupKind,
NotificationType,

// Namespaces
internal: {
sourcegraphURL: new URL('https://sourcegraph.test'),
clientApplication: 'sourcegraph' as const,
sync: () => Promise.resolve(),
updateContext: sinon.spy((updates: sourcegraph.ContextValues): void => undefined),
},
workspace: {
onDidOpenTextDocument: openedTextDocuments,
openedTextDocuments,
textDocuments: [] as sourcegraph.TextDocument[],
onDidChangeRoots: rootChanges,
rootChanges,
roots: [] as sourcegraph.WorkspaceRoot[],
},
languages: {
registerHoverProvider: sinon.spy(
(selector: sourcegraph.DocumentSelector, provider: sourcegraph.HoverProvider) => new Subscription()
),
registerDefinitionProvider: sinon.spy(
(selector: sourcegraph.DocumentSelector, provider: sourcegraph.DefinitionProvider) => new Subscription()
),
registerLocationProvider: sinon.spy(
(id: string, selector: sourcegraph.DocumentSelector, provider: sourcegraph.LocationProvider) =>
new Subscription()
),
registerReferenceProvider: sinon.spy(
(selector: sourcegraph.DocumentSelector, provider: sourcegraph.ReferenceProvider) => new Subscription()
),
registerCompletionItemProvider: sinon.spy(
(selector: sourcegraph.DocumentSelector, provider: sourcegraph.CompletionItemProvider) =>
new Subscription()
),
registerTypeDefinitionProvider: sinon.spy(
deprecate(
(selector: sourcegraph.DocumentSelector, provider: DeprecatedTypeDefinitionProvider) =>
new Subscription(),
'sourcegraph.languages.registerTypeDefinitionProvider() is deprecated. Use sourcegraph.languages.registerLocationProvider() instead.'
)
),
registerImplementationProvider: sinon.spy(
deprecate(
(selector: sourcegraph.DocumentSelector, provider: DeprecatedImplementationProvider) =>
new Subscription(),
'sourcegraph.languages.registerImplementationProvider() is deprecated. Use sourcegraph.languages.registerLocationProvider() instead.'
)
),
},
app: {
windows: [] as sourcegraph.Window[],
get activeWindow(): sourcegraph.Window | undefined {
return this.activeWindowChanges.value
},
activeWindowChanges: new BehaviorSubject<sourcegraph.Window | undefined>(undefined),

createDecorationType: () => ({ key: 'decorationType' + decorationTypeCounter++ }),
createPanelView: notImplemented as ((id: string) => sourcegraph.PanelView),
},
configuration: {
get: <C extends object = { [key: string]: any }>(): sourcegraph.Configuration<C> => ({
get: <K extends keyof C>(key: K): Readonly<C[K]> | undefined => configSubject.value[key],
update: async <K extends keyof C>(key: K, value: C[K] | undefined): Promise<void> => {
configSubject.next({ ...configSubject.value, [key]: value })
},
get value(): Readonly<C> {
return configSubject.value
},
}),
subscribe: (next: () => void) => configSubject.pipe(mapTo(undefined)).subscribe(next),
},
content: {
registerLinkPreviewProvider: sinon.spy(
(urlMatchPattern: string, provider: sourcegraph.LinkPreviewProvider) => new Subscription()
),
},
search: {
registerQueryTransformer: sinon.spy((provider: sourcegraph.QueryTransformer) => new Subscription()),
},
commands: {
registerCommand: sinon.spy((command: string, callback: (...args: any[]) => any) => new Subscription()),
executeCommand: sinon.spy<(command: string, ...args: any[]) => Promise<any>>(notImplemented),
},
}
assertTypeIsCompatible<typeof sourcegraph>(stubs)
return stubs
}
24 changes: 24 additions & 0 deletions src/codeEditor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Position, Range, Selection } from '@sourcegraph/extension-api-classes'
import assert from 'assert'
import * as sinon from 'sinon'
import { createStubCodeEditor } from './codeEditor'
import { createStubTextDocument } from './textDocument'

describe('createStubCodeEditor()', () => {
it('should create a stub CodeEditor', () => {
const stub = createStubCodeEditor({
document: createStubTextDocument({ languageId: 'foo', text: 'bla', uri: 'foo:bar' }),
})

assert.strictEqual(stub.type, 'CodeEditor')

assert.strictEqual(stub.selection, null)
const newSelections = [new Selection(new Position(0, 1), new Position(1, 2))]
stub.selectionsChanges.next(newSelections)
assert.strictEqual(stub.selections, newSelections)
assert.strictEqual(stub.selection, newSelections[0])

stub.setDecorations({ key: 'foo' }, [{ range: new Range(0, 1, 2, 3), border: 'red' }])
sinon.assert.calledOnce(stub.setDecorations)
})
})
29 changes: 29 additions & 0 deletions src/codeEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BehaviorSubject } from 'rxjs'
import * as sinon from 'sinon'
import * as sourcegraph from 'sourcegraph'
import { assertTypeIsCompatible } from './util'

export const createStubCodeEditor = ({
document,
selections = [],
}: Pick<sourcegraph.CodeEditor, 'document'> & Partial<Pick<sourcegraph.CodeEditor, 'selections'>>) => {
const codeEditor = {
type: 'CodeEditor' as const,
document,
get selections(): sourcegraph.Selection[] {
return this.selectionsChanges.value
},
get selection(): sourcegraph.Selection | null {
return this.selections[0] || null
},
selectionsChanges: new BehaviorSubject<sourcegraph.Selection[]>(selections),
setDecorations: sinon.spy(
(
decorationType: sourcegraph.TextDocumentDecorationType,
decorations: sourcegraph.TextDocumentDecoration[]
): void => undefined
),
}
assertTypeIsCompatible<sourcegraph.CodeEditor>(codeEditor)
return codeEditor
}
14 changes: 14 additions & 0 deletions src/context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as sinon from 'sinon'
import { createStubExtensionContext } from './context'

describe('createStubExtensionContext()', () => {
it('should create an extension context', () => {
const ctx = createStubExtensionContext()
const fn = sinon.spy()
ctx.subscriptions.add(fn)
sinon.assert.notCalled(fn)
sinon.assert.calledOnce(ctx.subscriptions.add)
ctx.subscriptions.unsubscribe()
sinon.assert.calledOnce(fn)
})
})
16 changes: 16 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Subscription } from 'rxjs'
import * as sinon from 'sinon'
import { ExtensionContext } from 'sourcegraph'
import { assertTypeIsCompatible } from './util'

export const createStubExtensionContext = () => {
const subscriptions = sinon.stub(new Subscription())
subscriptions.add.callThrough()
subscriptions.remove.callThrough()
subscriptions.unsubscribe.callThrough()
const context = {
subscriptions,
}
assertTypeIsCompatible<ExtensionContext>(context)
return context
}

0 comments on commit 01b12ab

Please sign in to comment.