Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/providers/completion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { describe, expect, it, vi } from 'vitest'
import type { IndexManager } from '../component-detection/index-manager'
import type { BaseUiData } from '../data/types'

vi.mock('vscode', () => {
class Position {
constructor(
public line: number,
public character: number,
) {}
translate(_lineDelta: number, charDelta: number) {
return new Position(this.line, this.character + charDelta)
}
}

class Range {
constructor(
public start: Position,
public end: Position,
) {}
}

class CompletionItem {
label: string
kind: number
detail?: string
documentation?: unknown
sortText?: string
filterText?: string
insertText?: string
range?: Range
constructor(label: string, kind: number) {
this.label = label
this.kind = kind
}
}

class MarkdownString {
value = ''
appendMarkdown(s: string) {
this.value += s
return this
}
}
return {
Position,
Range,
CompletionItem,
MarkdownString,
CompletionItemKind: { Property: 9, EnumMember: 11, Variable: 5 },
}
})

import { BaseUiCompletionProvider } from './completion'

const MOCK_DATA: BaseUiData = {
version: '1.0.0',
components: {
Dialog: {
attributes: [
{ name: 'data-open', values: [{ value: 'true' }, { value: 'false' }] },
],
cssVariables: [{ name: '--dialog-z-index' }],
attributesSourceFile: undefined,
cssVarsSourceFile: undefined,
},
Tooltip: {
attributes: [
{ name: 'data-side', values: [{ value: 'top' }, { value: 'bottom' }] },
],
cssVariables: [{ name: '--tooltip-z-index' }],
attributesSourceFile: undefined,
cssVarsSourceFile: undefined,
},
},
}

function makeProvider() {
return new BaseUiCompletionProvider(MOCK_DATA, {} as unknown as IndexManager)
}

function fakePosition() {
return {
line: 0,
character: 10,
translate: (_lineDelta: number, charDelta: number) => ({
line: 0,
character: 10 + charDelta,
}),
} as unknown as import('vscode').Position
}

describe('attributeNameCompletions', () => {
it('returns all attributes when scopeComponents is null (no scope restriction)', () => {
const provider = makeProvider()
const items = (provider as any).attributeNameCompletions(
'data-',
fakePosition(),
null,
)
const names = items.map((i: { label: string }) => i.label)
expect(names).toContain('data-open')
expect(names).toContain('data-side')
})

it('returns no attributes when scopeComponents is empty (unknown class)', () => {
const provider = makeProvider()
const items = (provider as any).attributeNameCompletions(
'data-',
fakePosition(),
[],
)
expect(items).toHaveLength(0)
})

it('filters to matching component when scopeComponents is set', () => {
const provider = makeProvider()
const items = (provider as any).attributeNameCompletions(
'data-',
fakePosition(),
['Dialog'],
)
const names = items.map((i: { label: string }) => i.label)
expect(names).toContain('data-open')
expect(names).not.toContain('data-side')
})
})

describe('cssVariableCompletions', () => {
it('returns all css vars when scopeComponents is null (no scope restriction)', () => {
const provider = makeProvider()
const items = (provider as any).cssVariableCompletions(
'--',
fakePosition(),
null,
false,
)
const names = items.map((i: { label: string }) => i.label)
expect(names).toContain('--dialog-z-index')
expect(names).toContain('--tooltip-z-index')
})

it('returns no css vars when scopeComponents is empty (unknown class)', () => {
const provider = makeProvider()
const items = (provider as any).cssVariableCompletions(
'--',
fakePosition(),
[],
false,
)
expect(items).toHaveLength(0)
})

it('filters to matching component when scopeComponents is set', () => {
const provider = makeProvider()
const items = (provider as any).cssVariableCompletions(
'--',
fakePosition(),
['Tooltip'],
false,
)
const names = items.map((i: { label: string }) => i.label)
expect(names).toContain('--tooltip-z-index')
expect(names).not.toContain('--dialog-z-index')
})
})
10 changes: 5 additions & 5 deletions src/providers/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider {
`[base-ui] provideCompletionItems lang=${document.languageId} ctx=${JSON.stringify(ctx)}`,
)

let scopeComponents: string[] = []
let scopeComponents: string[] | null = null
if (
ctx.kind !== 'none' &&
ctx.selectorScope !== null &&
Expand Down Expand Up @@ -128,14 +128,14 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider {
private attributeNameCompletions(
prefix: string,
position: vscode.Position,
scopeComponents: string[],
scopeComponents: string[] | null,
): vscode.CompletionItem[] {
const range = this.prefixRange(prefix, position)
return this.attributeIndex
.filter((entry) => entry.attribute.name.startsWith(prefix))
.filter(
(entry) =>
scopeComponents.length === 0 ||
scopeComponents === null ||
entry.components.some((c) => scopeComponents.includes(c)),
)
.map((entry) => {
Expand Down Expand Up @@ -181,15 +181,15 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider {
private cssVariableCompletions(
prefix: string,
position: vscode.Position,
scopeComponents: string[],
scopeComponents: string[] | null,
needsVarWrapper: boolean,
): vscode.CompletionItem[] {
const range = this.prefixRange(prefix, position)
return this.cssVarIndex
.filter((entry) => entry.cssVar.name.startsWith(prefix))
.filter(
(entry) =>
scopeComponents.length === 0 ||
scopeComponents === null ||
entry.components.some((c) => scopeComponents.includes(c)),
)
.map((entry) => {
Expand Down