import { TextDocument } from 'vscode-languageserver-textdocument';
import {
parseHtmlDocument,
getAllElements,
getElementAtOffset,
getCursorContext,
hasNoJsDirectives,
findTemplates,
findRefs,
findStores
} from '../../server/src/html-parser';
function createDocument(content: string): TextDocument {
return TextDocument.create('file:///test.html', 'html', 1, content);
}
describe('HtmlParser', () => {
describe('parseHtmlDocument', () => {
it('parses a simple HTML document', () => {
const doc = createDocument('
');
const htmlDoc = parseHtmlDocument(doc);
expect(htmlDoc.roots.length).toBeGreaterThan(0);
});
});
describe('getAllElements', () => {
it('extracts elements with attributes', () => {
const content = '
';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const elements = getAllElements(htmlDoc, content);
expect(elements.length).toBe(2);
expect(elements[0].tag).toBe('div');
expect(elements[1].tag).toBe('span');
});
it('extracts attribute names and values', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const elements = getAllElements(htmlDoc, content);
expect(elements[0].attributes.length).toBe(2);
const stateAttr = elements[0].attributes.find(a => a.name === 'state');
expect(stateAttr).toBeDefined();
});
});
describe('getElementAtOffset', () => {
it('returns element at offset', () => {
const content = '
';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
// Offset inside the opening tag
const element = getElementAtOffset(htmlDoc, 5, content);
expect(element).toBeDefined();
expect(element!.tag).toBe('div');
});
it('returns inner element for nested offset', () => {
const content = '
';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
// Offset inside
opening tag
const element = getElementAtOffset(htmlDoc, 10, content);
expect(element).toBeDefined();
expect(element!.tag).toBe('span');
});
});
describe('getCursorContext', () => {
it('detects attribute name context', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const position = doc.positionAt(8); // after "sta"
const ctx = getCursorContext(doc, position, htmlDoc);
expect(ctx.type).toBe('attributeName');
if (ctx.type === 'attributeName') {
expect(ctx.partial).toMatch(/^sta/);
}
});
it('detects attribute value context', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const position = doc.positionAt(15); // inside the value
const ctx = getCursorContext(doc, position, htmlDoc);
expect(ctx.type).toBe('attributeValue');
if (ctx.type === 'attributeValue') {
expect(ctx.attrName).toBe('state');
}
});
it('returns none outside tags', () => {
const content = ' some text here';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const position = doc.positionAt(20); // in text content
const ctx = getCursorContext(doc, position, htmlDoc);
expect(ctx.type).toBe('none');
});
});
describe('hasNoJsDirectives', () => {
it('detects state directive', () => {
expect(hasNoJsDirectives('')).toBe(true);
});
it('detects on: pattern', () => {
expect(hasNoJsDirectives('')).toBe(true);
});
it('detects bind- pattern', () => {
expect(hasNoJsDirectives('Link')).toBe(true);
});
it('detects NoJS script tag', () => {
expect(hasNoJsDirectives('')).toBe(true);
});
it('returns false for plain HTML', () => {
expect(hasNoJsDirectives('')).toBe(false);
});
});
describe('findTemplates', () => {
it('finds template elements with IDs', () => {
const content = 'card
';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const templates = findTemplates(htmlDoc, content);
expect(templates.size).toBe(2);
expect(templates.has('card')).toBe(true);
expect(templates.has('list')).toBe(true);
});
});
describe('findRefs', () => {
it('finds ref attributes', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const refs = findRefs(htmlDoc, content);
expect(refs.size).toBe(2);
expect(refs.has('nameInput')).toBe(true);
expect(refs.has('container')).toBe(true);
});
});
describe('findStores', () => {
it('finds store attribute declarations', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const stores = findStores(htmlDoc, content);
expect(stores.size).toBe(1);
expect(stores.has('cart')).toBe(true);
});
it('finds stores declared in NoJS.config()', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const stores = findStores(htmlDoc, content);
expect(stores.has('auth')).toBe(true);
expect(stores.has('ui')).toBe(true);
});
it('prefers store attribute over config store', () => {
const content = '';
const doc = createDocument(content);
const htmlDoc = parseHtmlDocument(doc);
const stores = findStores(htmlDoc, content);
expect(stores.size).toBe(1);
// Should point to the HTML element, not the config block
expect(stores.get('auth')!.start).toBe(0);
});
});
});