Skip to content

Commit

Permalink
Basic tests for multifile schemas (#1731)
Browse files Browse the repository at this point in the history
* Basic tests for multifile schemas

* Fix windows test

* fix: Newline characters should be a part of untrimmed lines

* Fix characterAfter, add failing test for empty completion

* Fix `@map` spacing

* Revert windows hack

* Windows, please
  • Loading branch information
SevInf committed May 16, 2024
1 parent 281e45a commit fe081ac
Show file tree
Hide file tree
Showing 27 changed files with 601 additions and 37 deletions.
18 changes: 16 additions & 2 deletions packages/language-server/src/__test__/helper.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import * as fs from 'fs'
import { Position, TextDocument } from 'vscode-languageserver-textdocument'
import { loadSchemaFiles } from '@prisma/schema-files-loader'
import path from 'path'
import { URI } from 'vscode-uri'
import { PrismaSchema, SchemaDocument } from '../lib/Schema'

export const CURSOR_CHARACTER = '|'

const fixturesDir = path.resolve(__dirname, '../../test/fixtures')
const multifileFixturesDir = path.join(fixturesDir, 'multifile')

export function getTextDocument(testFilePath: string): TextDocument {
const absPath = path.join(fixturesDir, testFilePath)
const content: string = fs.readFileSync(absPath, 'utf8')

return TextDocument.create(fixturePathToUri(testFilePath), 'prisma', 1, content)
return TextDocument.create(fixturePathToUri(absPath), 'prisma', 1, content)
}

export async function getMultifileSchema(folderPath: string): Promise<PrismaSchema> {
const files = await loadSchemaFiles(path.join(multifileFixturesDir, folderPath))
const schemaDocs = files.map(([filePath, content]) => {
const uri = fixturePathToUri(filePath)
const doc = TextDocument.create(uri, 'prisma', 1, content)
return new SchemaDocument(doc)
})

return new PrismaSchema(schemaDocs)
}

export function fixturePathToUri(fixturePath: string) {
const absPath = path.join(fixturesDir, fixturePath)
const absPath = path.isAbsolute(fixturePath) ? fixturePath : path.join(fixturesDir, fixturePath)

// that would normalize testFilePath and resolve all of
// the . and ..
Expand Down
83 changes: 83 additions & 0 deletions packages/language-server/src/__test__/multifile/MultifileHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { URI } from 'vscode-uri'
import { PrismaSchema, SchemaDocument } from '../../lib/Schema'
import path from 'path'
import { getMultifileSchema } from '../helper'
import { Position, TextEdit } from 'vscode-languageserver'
import { TextDocument } from 'vscode-languageserver-textdocument'

export async function getMultifileHelper(fixturePath: string) {
const schema = await getMultifileSchema(fixturePath)
const helper = new MultfileHelper(`/multifile/${fixturePath}`, schema)
return helper
}

class MultfileHelper {
constructor(
private baseDir: string,
readonly schema: PrismaSchema,
) {}

file(filePath: string) {
const doc = this.schema.findDocByUri(URI.file(path.join(this.baseDir, filePath)).toString())
if (!doc) {
throw new Error(`${filePath} is not found fixture`)
}

return new File(doc)
}

applyChanges(edits: Record<string, TextEdit[]> | undefined): Record<string, string> {
if (!edits) {
return {}
}
const results = {} as Record<string, string>
for (const [uri, fileEdits] of Object.entries(edits)) {
let doc = this.schema.findDocByUri(uri)?.textDocument
if (!doc) {
doc = TextDocument.create(uri, 'prisma', 1, '')
}
const updatedDoc = TextDocument.applyEdits(doc, fileEdits)
results[uri] = updatedDoc
}
return results
}
}

class File {
constructor(readonly schemaDocument: SchemaDocument) {}

get textDocument() {
return this.schemaDocument.textDocument
}

get uri() {
return this.schemaDocument.uri
}

lineContaining(match: string) {
for (const line of this.schemaDocument.lines) {
if (line.text.includes(match)) {
return new Line(line.lineIndex, line.untrimmedText)
}
}
throw new Error(`Can not find line with "${match}"`)
}
}

class Line {
constructor(
readonly lineNumber: number,
readonly text: string,
) {}

characterAfter(substring: string): Position {
const index = this.text.indexOf(substring)
if (index === -1) {
throw new Error(`Can not find ${substring} in ${this.text}`)
}
return {
line: this.lineNumber,
character: index + substring.length + 1,
}
}
}
33 changes: 33 additions & 0 deletions packages/language-server/src/__test__/multifile/completion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { test, expect } from 'vitest'
import { handleCompletionRequest } from '../../lib/MessageHandler'
import { getMultifileHelper } from './MultifileHelper'

test('type name completion', async () => {
const helper = await getMultifileHelper('complete-field')
const post = helper.file('Post.prisma')

const response = handleCompletionRequest(helper.schema, post.textDocument, {
textDocument: {
uri: post.uri,
},
position: post.lineContaining('author Us').characterAfter('U'),
})

const userItem = response?.items.find((item) => item.label === 'User')
expect(userItem).not.toBeUndefined()
})

test('type name completion with no name typed', async () => {
const helper = await getMultifileHelper('complete-field')
const post = helper.file('Post.prisma')

const response = handleCompletionRequest(helper.schema, post.textDocument, {
textDocument: {
uri: post.uri,
},
position: post.lineContaining('author ').characterAfter('r '),
})

const userItem = response?.items.find((item) => item.label === 'User')
expect(userItem).not.toBeUndefined()
})
21 changes: 21 additions & 0 deletions packages/language-server/src/__test__/multifile/hover.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test, expect } from 'vitest'
import { handleHoverRequest } from '../../lib/MessageHandler'
import { getMultifileHelper } from './MultifileHelper'

test('basic doc', async () => {
const helper = await getMultifileHelper('user-posts')
const user = helper.file('User.prisma')

const response = handleHoverRequest(helper.schema, user.textDocument, {
textDocument: {
uri: user.uri,
},
position: user.lineContaining('posts Post[]').characterAfter('Po'),
})

expect(response).toMatchInlineSnapshot(`
{
"contents": "This is a blog post",
}
`)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { test, expect } from 'vitest'
import { handleDefinitionRequest } from '../../lib/MessageHandler'
import { getMultifileHelper } from './MultifileHelper'

test('basic doc', async () => {
const helper = await getMultifileHelper('user-posts')
const post = helper.file('Post.prisma')

const response = handleDefinitionRequest(helper.schema, post.textDocument, {
textDocument: {
uri: post.uri,
},
position: post.lineContaining('author User').characterAfter('Us'),
})

expect(response).toMatchInlineSnapshot(`
[
{
"targetRange": {
"end": {
"character": 1,
"line": 6,
},
"start": {
"character": 0,
"line": 1,
},
},
"targetSelectionRange": {
"end": {
"character": 10,
"line": 1,
},
"start": {
"character": 6,
"line": 1,
},
},
"targetUri": "file:///multifile/user-posts/User.prisma",
},
]
`)
})
35 changes: 35 additions & 0 deletions packages/language-server/src/__test__/multifile/linting.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test, expect } from 'vitest'
import { handleDiagnosticsRequest } from '../../lib/MessageHandler'
import { getMultifileHelper } from './MultifileHelper'

test('invalid doc', async () => {
const helper = await getMultifileHelper('linting')

const response = handleDiagnosticsRequest(helper.schema)

expect(response).toMatchInlineSnapshot(`
DiagnosticMap {
"_map": Map {
"file:///multifile/linting/Post.prisma" => [
{
"message": "Type "Like" is neither a built-in type, nor refers to another model, composite type, or enum.",
"range": {
"end": {
"character": 17,
"line": 7,
},
"start": {
"character": 13,
"line": 7,
},
},
"severity": 1,
"source": "Prisma",
},
],
"file:///multifile/linting/User.prisma" => [],
"file:///multifile/linting/config.prisma" => [],
},
}
`)
})
35 changes: 35 additions & 0 deletions packages/language-server/src/__test__/multifile/quickFix.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test, expect } from 'vitest'
import { handleCodeActions, handleDiagnosticsRequest } from '../../lib/MessageHandler'
import { getMultifileHelper } from './MultifileHelper'

test('basic doc', async () => {
const helper = await getMultifileHelper('quick-fix')
const profile = helper.file('Profile.prisma')
const diagnostics = handleDiagnosticsRequest(helper.schema).get(profile.uri)

const response = handleCodeActions(helper.schema, profile.textDocument, {
textDocument: {
uri: profile.uri,
},
range: {
start: profile.lineContaining('user User').characterAfter('us'),
end: profile.lineContaining('user User').characterAfter('user'),
},
context: {
diagnostics,
},
})

const changes = response[0]?.edit?.changes
const updated = helper.applyChanges(changes)
expect(updated).toMatchInlineSnapshot(`
{
"file:///multifile/quick-fix/Profile.prisma": "model Profile {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id])
}
",
}
`)
})

0 comments on commit fe081ac

Please sign in to comment.