loads of UI improvements, also improves the console UI
This commit is contained in:
108
test/symbols.test.ts
Normal file
108
test/symbols.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { isImportLine, parseDeclaration, parseNamespace, parseTraitUse } from '../src/main/symbols-service'
|
||||
import { wordAt } from '../src/renderer/src/editor'
|
||||
|
||||
describe('parseDeclaration', () => {
|
||||
it('reads every declaration keyword', () => {
|
||||
expect(parseDeclaration('class Agent')).toMatchObject({ kind: 'class', name: 'Agent' })
|
||||
expect(parseDeclaration('interface Searchable {')).toMatchObject({ kind: 'interface', name: 'Searchable' })
|
||||
expect(parseDeclaration('trait HasVault')).toMatchObject({ kind: 'trait', name: 'HasVault' })
|
||||
expect(parseDeclaration('enum Status: string')).toMatchObject({ kind: 'enum', name: 'Status' })
|
||||
})
|
||||
it('reads the parents off the declaration line, however many', () => {
|
||||
const d = parseDeclaration('final class VaultSearch extends Base implements Searchable, Arrayable, Jsonable {')
|
||||
expect(d?.parents).toEqual(['Base'])
|
||||
expect(d?.interfaces).toEqual(['Searchable', 'Arrayable', 'Jsonable'])
|
||||
})
|
||||
it('drops the namespace of a fully qualified parent', () => {
|
||||
const d = parseDeclaration('class A extends \\App\\Base implements \\App\\Contracts\\Runs')
|
||||
expect(d?.parents).toEqual(['Base'])
|
||||
expect(d?.interfaces).toEqual(['Runs'])
|
||||
})
|
||||
it('reads an interface extending several interfaces, and an enum contract', () => {
|
||||
expect(parseDeclaration('interface A extends B, C')?.parents).toEqual(['B', 'C'])
|
||||
expect(parseDeclaration('enum Status: string implements HasLabel')?.interfaces).toEqual(['HasLabel'])
|
||||
})
|
||||
it('leaves the lists empty when there is no clause', () => {
|
||||
const d = parseDeclaration('class Plain {')
|
||||
expect(d?.parents).toEqual([])
|
||||
expect(d?.interfaces).toEqual([])
|
||||
})
|
||||
it('accepts the modifiers PHP allows in front', () => {
|
||||
expect(parseDeclaration('final class A extends B')?.name).toBe('A')
|
||||
expect(parseDeclaration('abstract class A')?.name).toBe('A')
|
||||
expect(parseDeclaration('final readonly class A')?.name).toBe('A')
|
||||
expect(parseDeclaration(' class Nested')?.name).toBe('Nested')
|
||||
})
|
||||
it('ignores a mention that is not a declaration', () => {
|
||||
expect(parseDeclaration('$x = Agent::class;')).toBeNull()
|
||||
expect(parseDeclaration('// class Agent lives here')).toBeNull()
|
||||
expect(parseDeclaration('use App\\Models\\Agent;')).toBeNull()
|
||||
expect(parseDeclaration('return $a instanceof Agent;')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isImportLine', () => {
|
||||
it('drops a namespace import', () => {
|
||||
expect(isImportLine('use App\\Models\\Agent;')).toBe(true)
|
||||
expect(isImportLine('use App\\Support\\Str as S;')).toBe(true)
|
||||
expect(isImportLine('use function App\\Support\\tap;')).toBe(true)
|
||||
})
|
||||
it('keeps a trait mixed into a class body', () => {
|
||||
// The indent is the whole difference: a trait `use` is inside the class.
|
||||
expect(isImportLine(' use HasFactory;')).toBe(false)
|
||||
expect(isImportLine('\tuse HasFactory;')).toBe(false)
|
||||
})
|
||||
it('keeps real code that merely contains the word', () => {
|
||||
expect(isImportLine('$f = function () use ($agent) {};')).toBe(false)
|
||||
expect(isImportLine('// use Agent for this')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseNamespace', () => {
|
||||
it('reads the namespace of a file', () => {
|
||||
expect(parseNamespace('namespace App\\Console\\Commands;')).toBe('App\\Console\\Commands')
|
||||
expect(parseNamespace('namespace App;')).toBe('App')
|
||||
})
|
||||
it('returns null for anything else', () => {
|
||||
expect(parseNamespace('use App\\Models\\Agent;')).toBeNull()
|
||||
expect(parseNamespace('class A {')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseTraitUse', () => {
|
||||
it('reads one or several traits from an indented use', () => {
|
||||
expect(parseTraitUse(' use HasFactory;')).toEqual(['HasFactory'])
|
||||
expect(parseTraitUse(' use HasFactory, Notifiable, SoftDeletes;')).toEqual(['HasFactory', 'Notifiable', 'SoftDeletes'])
|
||||
expect(parseTraitUse('\tuse App\\Support\\HasVault;')).toEqual(['HasVault'])
|
||||
})
|
||||
it('reads a use with a conflict-resolution block', () => {
|
||||
expect(parseTraitUse(' use A, B { A::run insteadof B; }')).toEqual(['A', 'B'])
|
||||
})
|
||||
it('ignores what is not a trait', () => {
|
||||
expect(parseTraitUse('use App\\Models\\Agent;')).toEqual([]) // import, not indented
|
||||
expect(parseTraitUse(' use function App\\tap;')).toEqual([])
|
||||
expect(parseTraitUse(' $f = function () use ($x) {};')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('wordAt', () => {
|
||||
const line = 'return new Agent($x);'
|
||||
it('reads the identifier under the caret', () => {
|
||||
expect(wordAt(line, 12)).toBe('Agent') // inside
|
||||
expect(wordAt(line, 11)).toBe('Agent') // left edge
|
||||
expect(wordAt(line, 16)).toBe('Agent') // right edge
|
||||
})
|
||||
it('returns empty off an identifier', () => {
|
||||
expect(wordAt(line, 17)).toBe('') // inside "($x"
|
||||
expect(wordAt(' ', 1)).toBe('')
|
||||
})
|
||||
it('rejects a word that cannot be a class name', () => {
|
||||
expect(wordAt('$x = 42;', 6)).toBe('') // digits only
|
||||
})
|
||||
it('handles a caret at the string edges', () => {
|
||||
expect(wordAt('Agent', 0)).toBe('Agent')
|
||||
expect(wordAt('Agent', 5)).toBe('Agent')
|
||||
expect(wordAt('Agent', 9)).toBe('')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user