mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
[html] all code completion tests green
This commit is contained in:
parent
9717f397f5
commit
bb9a0f89ad
|
@ -6,12 +6,16 @@
|
|||
|
||||
import { TokenType, createScanner } from './htmlScanner';
|
||||
import { findFirst } from '../utils/arrays';
|
||||
import { isEmptyElement } from './htmlTags';
|
||||
import { isEmptyElement, isSameTag } from './htmlTags';
|
||||
|
||||
export class Node {
|
||||
public tag: string;
|
||||
public closed: boolean;
|
||||
constructor(public start: number, public end: number, public children: Node[], public parent: Node) {
|
||||
this.closed = false;
|
||||
}
|
||||
private get lastChild(): Node { return this.children.length ? this.children[this.children.length - 1] : void 0; }
|
||||
|
||||
public findNodeBefore(offset:number) : Node {
|
||||
let idx = findFirst(this.children, c => offset <= c.start) - 1;
|
||||
if (idx >= 0) {
|
||||
|
@ -20,6 +24,10 @@ export class Node {
|
|||
if (offset < child.end) {
|
||||
return child.findNodeBefore(offset);
|
||||
}
|
||||
let lastChild = child.lastChild;
|
||||
if (lastChild && lastChild.end === child.end) {
|
||||
return child.findNodeBefore(offset);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +45,7 @@ export function parse(text: string) : HTMLDocument {
|
|||
|
||||
let htmlDocument = new Node(0, text.length, [], null);
|
||||
let curr = htmlDocument;
|
||||
|
||||
let endTagStart : number = -1;
|
||||
let token = scanner.scan();
|
||||
while (token !== TokenType.EOS) {
|
||||
switch (token) {
|
||||
|
@ -52,16 +60,29 @@ export function parse(text: string) : HTMLDocument {
|
|||
case TokenType.StartTagClose:
|
||||
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
|
||||
if (isEmptyElement(curr.tag) && curr !== htmlDocument) {
|
||||
curr.closed = true;
|
||||
curr = curr.parent;
|
||||
}
|
||||
break;
|
||||
case TokenType.EndTagOpen:
|
||||
endTagStart = scanner.getTokenOffset();
|
||||
break;
|
||||
case TokenType.EndTag:
|
||||
let closeTag = scanner.getTokenText();
|
||||
while (curr.tag !== closeTag && curr !== htmlDocument) {
|
||||
while (!isSameTag(curr.tag, closeTag) && curr !== htmlDocument) {
|
||||
curr.end = endTagStart;
|
||||
curr.closed = false;
|
||||
curr = curr.parent;
|
||||
}
|
||||
if (curr !== htmlDocument) {
|
||||
curr.closed = true;
|
||||
}
|
||||
break;
|
||||
case TokenType.StartTagSelfClose:
|
||||
if (curr !== htmlDocument) {
|
||||
curr.closed = true;
|
||||
}
|
||||
// fallthrough
|
||||
case TokenType.EndTagClose:
|
||||
if (curr !== htmlDocument) {
|
||||
curr.end = scanner.getTokenEnd();
|
||||
|
|
|
@ -285,6 +285,7 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc
|
|||
return finishToken(offset, TokenType.Whitespace, localize('error.unexpectedWhitespace', 'Tag name must directly follow the open bracket.'));
|
||||
}
|
||||
stream.advanceUntilChar(_RAN);
|
||||
state = ScannerState.WithinEndTag;
|
||||
return finishToken(offset, TokenType.Unknown, localize('error.endTagNameExpected', 'End tag name expected.'));
|
||||
case ScannerState.WithinEndTag:
|
||||
if (stream.skipWhitespace()) { // white space is valid here
|
||||
|
@ -307,6 +308,7 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc
|
|||
return finishToken(offset, TokenType.Whitespace, localize('error.unexpectedWhitespace', 'Tag name must directly follow the open bracket.'));
|
||||
}
|
||||
stream.advanceUntilChar(_RAN);
|
||||
state = ScannerState.WithinTag;
|
||||
return finishToken(offset, TokenType.Unknown, localize('error.startTagNameExpected', 'Start tag name expected.'));
|
||||
case ScannerState.WithinTag:
|
||||
if (stream.skipWhitespace()) {
|
||||
|
|
|
@ -42,9 +42,12 @@ let localize = nls.loadMessageBundle();
|
|||
export const EMPTY_ELEMENTS:string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
|
||||
|
||||
export function isEmptyElement(e: string) : boolean {
|
||||
return arrays.binarySearch(EMPTY_ELEMENTS, e,(s1: string, s2: string) => s1.localeCompare(s2)) >= 0;
|
||||
return e && arrays.binarySearch(EMPTY_ELEMENTS, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0;
|
||||
}
|
||||
|
||||
export function isSameTag(t1: string, t2: string) : boolean {
|
||||
return t1 && t2 && t1.toLowerCase() === t2.toLowerCase();
|
||||
}
|
||||
|
||||
export interface IHTMLTagProvider {
|
||||
collectTags(collector: (tag: string, label: string) => void): void;
|
||||
|
|
|
@ -32,9 +32,15 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
let currentTag: string;
|
||||
let currentAttributeName: string;
|
||||
|
||||
function getReplaceRange(replaceStart: number) : Range {
|
||||
if (replaceStart > offset) {
|
||||
replaceStart = offset;
|
||||
}
|
||||
return { start: document.positionAt(replaceStart), end: document.positionAt(offset)};
|
||||
}
|
||||
|
||||
function collectOpenTagSuggestions(afterOpenBracket: number) : CompletionList {
|
||||
let range : Range = { start: document.positionAt(afterOpenBracket), end: document.positionAt(offset)};
|
||||
let range = getReplaceRange(afterOpenBracket);
|
||||
tagProviders.forEach((provider) => {
|
||||
provider.collectTags((tag, label) => {
|
||||
result.items.push({
|
||||
|
@ -49,21 +55,25 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
}
|
||||
|
||||
function collectCloseTagSuggestions(afterOpenBracket: number, matchingOnly: boolean) : CompletionList {
|
||||
let range : Range = { start: document.positionAt(afterOpenBracket), end: document.positionAt(offset)};
|
||||
let range = getReplaceRange(afterOpenBracket);
|
||||
let contentAfter = document.getText().substr(offset);
|
||||
let closeTag = isWhiteSpace(contentAfter) || startsWith(contentAfter, '<') ? '>' : '';
|
||||
if (node.parent && node.parent.tag) {
|
||||
let tag = node.parent.tag;
|
||||
result.items.push({
|
||||
label: '/' + tag,
|
||||
kind: CompletionItemKind.Property,
|
||||
filterText: '/' + tag + closeTag,
|
||||
textEdit: { newText: '/' + tag + closeTag, range: range }
|
||||
});
|
||||
return;
|
||||
let curr = node;
|
||||
while (curr) {
|
||||
let tag = curr.tag;
|
||||
if (tag && !curr.closed) {
|
||||
result.items.push({
|
||||
label: '/' + tag,
|
||||
kind: CompletionItemKind.Property,
|
||||
filterText: '/' + tag + closeTag,
|
||||
textEdit: { newText: '/' + tag + closeTag, range: range }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
curr = curr.parent;
|
||||
}
|
||||
if (matchingOnly) {
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
tagProviders.forEach((provider) => {
|
||||
|
@ -87,7 +97,7 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
}
|
||||
|
||||
function collectAttributeNameSuggestions(nameStart: number) : CompletionList {
|
||||
let range : Range = { start: document.positionAt(nameStart), end: document.positionAt(offset)};
|
||||
let range = getReplaceRange(nameStart);
|
||||
tagProviders.forEach((provider) => {
|
||||
provider.collectAttributes(currentTag, (attribute, type) => {
|
||||
let codeSnippet = attribute;
|
||||
|
@ -105,7 +115,7 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
}
|
||||
|
||||
function collectAttributeValueSuggestions(valueStart: number) : CompletionList {
|
||||
let range : Range = { start: document.positionAt(valueStart), end: document.positionAt(offset)};
|
||||
let range = getReplaceRange(valueStart);
|
||||
tagProviders.forEach((provider) => {
|
||||
provider.collectValues(currentTag, currentAttributeName, (value) => {
|
||||
let codeSnippet = '"' + value + '"';
|
||||
|
@ -162,6 +172,8 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
return collectAttributeNameSuggestions(scanner.getTokenEnd());
|
||||
case ScannerState.BeforeAttributeValue:
|
||||
return collectAttributeValueSuggestions(scanner.getTokenEnd());
|
||||
case ScannerState.AfterOpeningEndTag:
|
||||
return collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -170,7 +182,21 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
|
|||
return collectCloseTagSuggestions(scanner.getTokenOffset() + 1, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenType.EndTag:
|
||||
if (offset <= scanner.getTokenEnd()) {
|
||||
let text = document.getText();
|
||||
let start = scanner.getTokenOffset() - 1;
|
||||
while (start >= 0) {
|
||||
let ch = text.charAt(start);
|
||||
if (ch === '/') {
|
||||
return collectCloseTagSuggestions(start, false);
|
||||
} else if (!isWhiteSpace(ch)) {
|
||||
break;
|
||||
}
|
||||
start--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
token = scanner.scan();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,12 @@ export let assertCompletion = function (completions: CompletionList, expected: I
|
|||
if (expected.insertText && typeof expected.overwriteBefore === 'number' && matches[0].textEdit) {
|
||||
let text = document.getText();
|
||||
let expectedText = text.substr(0, offset - expected.overwriteBefore) + expected.insertText + text.substr(offset);
|
||||
assert.equal(applyEdits(document, [matches[0].textEdit]), expectedText);
|
||||
let actualText = applyEdits(document, [matches[0].textEdit]);
|
||||
if (actualText !== expectedText) {
|
||||
assert.equal(actualText, expectedText);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -54,7 +59,7 @@ let testCompletionFor = function (value: string, expected: { count?: number, ite
|
|||
let ls = htmlLanguageService.getLanguageService();
|
||||
|
||||
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
|
||||
let position = Position.create(0, offset);
|
||||
let position = document.positionAt(offset);
|
||||
let jsonDoc = ls.parseHTMLDocument(document);
|
||||
return asPromise(ls.doComplete(document, position, jsonDoc)).then(list => {
|
||||
try {
|
||||
|
@ -160,255 +165,255 @@ suite('HTML Completion', () => {
|
|||
]
|
||||
}),
|
||||
|
||||
// testCompletionFor('<input type=|', {
|
||||
// items: [
|
||||
// { label: 'text', resultText: 'text' },
|
||||
// { label: 'checkbox', resultText: 'checkbox' },
|
||||
// ]
|
||||
// }),
|
||||
testCompletionFor('<input type=|', {
|
||||
items: [
|
||||
{ label: 'text', resultText: '<input type="text"' },
|
||||
{ label: 'checkbox', resultText: '<input type="checkbox"' },
|
||||
]
|
||||
}),
|
||||
|
||||
// testCompletionFor('<input type="c|', {
|
||||
// items: [
|
||||
// { label: 'color', resultText: 'color' },
|
||||
// { label: 'checkbox', resultText: 'checkbox' },
|
||||
// ]
|
||||
// }),
|
||||
testCompletionFor('<input type="c|', {
|
||||
items: [
|
||||
{ label: 'color', resultText: '<input type="color"' },
|
||||
{ label: 'checkbox', resultText: '<input type="checkbox"' },
|
||||
]
|
||||
}),
|
||||
|
||||
// testCompletionFor('<input type= |', {
|
||||
// items: [
|
||||
// { label: 'color', resultText: 'color' },
|
||||
// { label: 'checkbox', resultText: 'checkbox' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<input src="c" type="color|" ', {
|
||||
// items: [
|
||||
// { label: 'color', resultText: 'color' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<input src="c" type=color| ', {
|
||||
// items: [
|
||||
// { label: 'color', resultText: 'color' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<div dir=|></div>', {
|
||||
// items: [
|
||||
// { label: 'ltr', resultText: 'ltr' },
|
||||
// { label: 'rtl', resultText: 'rtl' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<ul><|>', {
|
||||
// items: [
|
||||
// { label: '/ul', resultText: '/ul' },
|
||||
// { label: 'li', resultText: 'li' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<ul><li><|', {
|
||||
// items: [
|
||||
// { label: '/li', resultText: '/li' },
|
||||
// { label: 'a', resultText: 'a' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<table></|>', {
|
||||
// items: [
|
||||
// { label: '/table', resultText: '/table' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<foo></f|', {
|
||||
// items: [
|
||||
// { label: '/foo', resultText: '/foo' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<ul></ |>', {
|
||||
// items: [
|
||||
// { label: '/ul', resultText: '/ul' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<span></ |', {
|
||||
// items: [
|
||||
// { label: '/span', resultText: '/span' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<li><br></ |>', {
|
||||
// items: [
|
||||
// { label: '/li', resultText: '/li' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<li><br/></ |>', {
|
||||
// items: [
|
||||
// { label: '/li', resultText: '/li' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<li><div/></|', {
|
||||
// items: [
|
||||
// { label: '/li', resultText: '/li' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<li><br/|>', { count: 0 }),
|
||||
// testCompletionFor('<li><br>a/|', { count: 0 }),
|
||||
testCompletionFor('<input type= |', {
|
||||
items: [
|
||||
{ label: 'color', resultText: '<input type= "color"' },
|
||||
{ label: 'checkbox', resultText: '<input type= "checkbox"' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<input src="c" type="color|" ', {
|
||||
items: [
|
||||
{ label: 'color', resultText: '<input src="c" type="color"" ' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<input src="c" type=color| ', {
|
||||
items: [
|
||||
{ label: 'color', resultText: '<input src="c" type="color" ' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<div dir=|></div>', {
|
||||
items: [
|
||||
{ label: 'ltr', resultText: '<div dir="ltr"></div>' },
|
||||
{ label: 'rtl', resultText: '<div dir="rtl"></div>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<ul><|>', {
|
||||
items: [
|
||||
{ label: '/ul', resultText: '<ul></ul>' },
|
||||
{ label: 'li', resultText: '<ul><li>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<ul><li><|', {
|
||||
items: [
|
||||
{ label: '/li', resultText: '<ul><li></li>' },
|
||||
{ label: 'a', resultText: '<ul><li><a' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<goo></|>', {
|
||||
items: [
|
||||
{ label: '/goo', resultText: '<goo></goo>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<foo></f|', {
|
||||
items: [
|
||||
{ label: '/foo', resultText: '<foo></foo>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<foo></ |>', {
|
||||
items: [
|
||||
{ label: '/foo', resultText: '<foo></foo>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<span></ s|', {
|
||||
items: [
|
||||
{ label: '/span', resultText: '<span></span>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<li><br></ |>', {
|
||||
items: [
|
||||
{ label: '/li', resultText: '<li><br></li>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<foo><br/></ f|>', {
|
||||
items: [
|
||||
{ label: '/foo', resultText: '<foo><br/></foo>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<li><div/></|', {
|
||||
items: [
|
||||
{ label: '/li', resultText: '<li><div/></li>' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<li><br/|>', { count: 0 }),
|
||||
testCompletionFor('<li><br>a/|', { count: 0 }),
|
||||
|
||||
// testCompletionFor('<a><div></div></| ', {
|
||||
// items: [
|
||||
// { label: '/a', resultText: '/a' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<body><div><div></div></div></| >', {
|
||||
// items: [
|
||||
// { label: '/body', resultText: '/body' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor(['<body>', ' <div>', ' </|'].join('\n'), {
|
||||
!/usr/bin/env [
|
||||
// { label: '/div', resultText: '/div' },
|
||||
// ]
|
||||
// })
|
||||
testCompletionFor('<foo><bar></bar></| ', {
|
||||
items: [
|
||||
{ label: '/foo', resultText: '<foo><bar></bar></foo> ' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<body><div><div></div></div></| >', {
|
||||
items: [
|
||||
{ label: '/body', resultText: '<body><div><div></div></div></body >' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor(['<body>', ' <div>', ' </|'].join('\n'), {
|
||||
items: [
|
||||
{ label: '/div', resultText: ['<body>', ' <div>', ' </div>'].join('\n') },
|
||||
]
|
||||
})
|
||||
], testDone);
|
||||
});
|
||||
|
||||
// test('Intellisense aria', function (testDone): any {
|
||||
// let expectedAriaAttributes = [
|
||||
// { label: 'aria-activedescendant' },
|
||||
// { label: 'aria-atomic' },
|
||||
// { label: 'aria-autocomplete' },
|
||||
// { label: 'aria-busy' },
|
||||
// { label: 'aria-checked' },
|
||||
// { label: 'aria-colcount' },
|
||||
// { label: 'aria-colindex' },
|
||||
// { label: 'aria-colspan' },
|
||||
// { label: 'aria-controls' },
|
||||
// { label: 'aria-current' },
|
||||
// { label: 'aria-describedat' },
|
||||
// { label: 'aria-describedby' },
|
||||
// { label: 'aria-disabled' },
|
||||
// { label: 'aria-dropeffect' },
|
||||
// { label: 'aria-errormessage' },
|
||||
// { label: 'aria-expanded' },
|
||||
// { label: 'aria-flowto' },
|
||||
// { label: 'aria-grabbed' },
|
||||
// { label: 'aria-haspopup' },
|
||||
// { label: 'aria-hidden' },
|
||||
// { label: 'aria-invalid' },
|
||||
// { label: 'aria-kbdshortcuts' },
|
||||
// { label: 'aria-label' },
|
||||
// { label: 'aria-labelledby' },
|
||||
// { label: 'aria-level' },
|
||||
// { label: 'aria-live' },
|
||||
// { label: 'aria-modal' },
|
||||
// { label: 'aria-multiline' },
|
||||
// { label: 'aria-multiselectable' },
|
||||
// { label: 'aria-orientation' },
|
||||
// { label: 'aria-owns' },
|
||||
// { label: 'aria-placeholder' },
|
||||
// { label: 'aria-posinset' },
|
||||
// { label: 'aria-pressed' },
|
||||
// { label: 'aria-readonly' },
|
||||
// { label: 'aria-relevant' },
|
||||
// { label: 'aria-required' },
|
||||
// { label: 'aria-roledescription' },
|
||||
// { label: 'aria-rowcount' },
|
||||
// { label: 'aria-rowindex' },
|
||||
// { label: 'aria-rowspan' },
|
||||
// { label: 'aria-selected' },
|
||||
// { label: 'aria-setsize' },
|
||||
// { label: 'aria-sort' },
|
||||
// { label: 'aria-valuemax' },
|
||||
// { label: 'aria-valuemin' },
|
||||
// { label: 'aria-valuenow' },
|
||||
// { label: 'aria-valuetext' }
|
||||
// ];
|
||||
// run([
|
||||
// testCompletionFor('<div |> </div >', { items: expectedAriaAttributes }),
|
||||
// testCompletionFor('<span |> </span >', { items: expectedAriaAttributes }),
|
||||
// testCompletionFor('<input |> </input >', { items: expectedAriaAttributes })
|
||||
// ], testDone);
|
||||
// });
|
||||
test('Intellisense aria', function (testDone): any {
|
||||
let expectedAriaAttributes = [
|
||||
{ label: 'aria-activedescendant' },
|
||||
{ label: 'aria-atomic' },
|
||||
{ label: 'aria-autocomplete' },
|
||||
{ label: 'aria-busy' },
|
||||
{ label: 'aria-checked' },
|
||||
{ label: 'aria-colcount' },
|
||||
{ label: 'aria-colindex' },
|
||||
{ label: 'aria-colspan' },
|
||||
{ label: 'aria-controls' },
|
||||
{ label: 'aria-current' },
|
||||
{ label: 'aria-describedat' },
|
||||
{ label: 'aria-describedby' },
|
||||
{ label: 'aria-disabled' },
|
||||
{ label: 'aria-dropeffect' },
|
||||
{ label: 'aria-errormessage' },
|
||||
{ label: 'aria-expanded' },
|
||||
{ label: 'aria-flowto' },
|
||||
{ label: 'aria-grabbed' },
|
||||
{ label: 'aria-haspopup' },
|
||||
{ label: 'aria-hidden' },
|
||||
{ label: 'aria-invalid' },
|
||||
{ label: 'aria-kbdshortcuts' },
|
||||
{ label: 'aria-label' },
|
||||
{ label: 'aria-labelledby' },
|
||||
{ label: 'aria-level' },
|
||||
{ label: 'aria-live' },
|
||||
{ label: 'aria-modal' },
|
||||
{ label: 'aria-multiline' },
|
||||
{ label: 'aria-multiselectable' },
|
||||
{ label: 'aria-orientation' },
|
||||
{ label: 'aria-owns' },
|
||||
{ label: 'aria-placeholder' },
|
||||
{ label: 'aria-posinset' },
|
||||
{ label: 'aria-pressed' },
|
||||
{ label: 'aria-readonly' },
|
||||
{ label: 'aria-relevant' },
|
||||
{ label: 'aria-required' },
|
||||
{ label: 'aria-roledescription' },
|
||||
{ label: 'aria-rowcount' },
|
||||
{ label: 'aria-rowindex' },
|
||||
{ label: 'aria-rowspan' },
|
||||
{ label: 'aria-selected' },
|
||||
{ label: 'aria-setsize' },
|
||||
{ label: 'aria-sort' },
|
||||
{ label: 'aria-valuemax' },
|
||||
{ label: 'aria-valuemin' },
|
||||
{ label: 'aria-valuenow' },
|
||||
{ label: 'aria-valuetext' }
|
||||
];
|
||||
run([
|
||||
testCompletionFor('<div |> </div >', { items: expectedAriaAttributes }),
|
||||
testCompletionFor('<span |> </span >', { items: expectedAriaAttributes }),
|
||||
testCompletionFor('<input |> </input >', { items: expectedAriaAttributes })
|
||||
], testDone);
|
||||
});
|
||||
|
||||
// test('Intellisense Angular', function (testDone): any {
|
||||
// run([
|
||||
// testCompletionFor('<body |> </body >', {
|
||||
// items: [
|
||||
// { label: 'ng-controller', resultText: 'ng-controller' },
|
||||
// { label: 'data-ng-controller', resultText: 'data-ng-controller' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<li |> </li >', {
|
||||
// items: [
|
||||
// { label: 'ng-repeat', resultText: 'ng-repeat' },
|
||||
// { label: 'data-ng-repeat', resultText: 'data-ng-repeat' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<input |> </input >', {
|
||||
// items: [
|
||||
// { label: 'ng-model', resultText: 'ng-model' },
|
||||
// { label: 'data-ng-model', resultText: 'data-ng-model' },
|
||||
// ]
|
||||
// })
|
||||
// ], testDone);
|
||||
// });
|
||||
test('Intellisense Angular', function (testDone): any {
|
||||
run([
|
||||
testCompletionFor('<body |> </body >', {
|
||||
items: [
|
||||
{ label: 'ng-controller', resultText: '<body ng-controller="{{}}"> </body >' },
|
||||
{ label: 'data-ng-controller', resultText: '<body data-ng-controller="{{}}"> </body >' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<li |> </li >', {
|
||||
items: [
|
||||
{ label: 'ng-repeat', resultText: '<li ng-repeat="{{}}"> </li >' },
|
||||
{ label: 'data-ng-repeat', resultText: '<li data-ng-repeat="{{}}"> </li >' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<input |> </input >', {
|
||||
items: [
|
||||
{ label: 'ng-model', resultText: '<input ng-model="{{}}"> </input >' },
|
||||
{ label: 'data-ng-model', resultText: '<input data-ng-model="{{}}"> </input >' },
|
||||
]
|
||||
})
|
||||
], testDone);
|
||||
});
|
||||
|
||||
// test('Intellisense Ionic', function (testDone): any {
|
||||
// run([
|
||||
// // Try some Ionic tags
|
||||
// testCompletionFor('<|', {
|
||||
// items: [
|
||||
// { label: 'ion-checkbox', resultText: 'ion-checkbox' },
|
||||
// { label: 'ion-content', resultText: 'ion-content' },
|
||||
// { label: 'ion-nav-title', resultText: 'ion-nav-title' },
|
||||
// ]
|
||||
// }),
|
||||
// testCompletionFor('<ion-re|', {
|
||||
// items: [
|
||||
// { label: 'ion-refresher', resultText: 'ion-refresher' },
|
||||
// { label: 'ion-reorder-button', resultText: 'ion-reorder-button' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try some global attributes (1 with value suggestions, 1 without value suggestions, 1 void)
|
||||
// testCompletionFor('<ion-checkbox |', {
|
||||
// items: [
|
||||
// { label: 'force-refresh-images', resultText: 'force-refresh-images' },
|
||||
// { label: 'collection-repeat', resultText: 'collection-repeat' },
|
||||
// { label: 'menu-close', resultText: 'menu-close' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try some tag-specific attributes (1 with value suggestions, 1 void)
|
||||
// testCompletionFor('<ion-footer-bar |', {
|
||||
// items: [
|
||||
// { label: 'align-title', resultText: 'align-title' },
|
||||
// { label: 'keyboard-attach', resultText: 'keyboard-attach' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try the extended attributes of an existing HTML 5 tag
|
||||
// testCompletionFor('<a |', {
|
||||
// items: [
|
||||
// { label: 'nav-direction', resultText: 'nav-direction' },
|
||||
// { label: 'nav-transition', resultText: 'nav-transition' },
|
||||
// { label: 'href', resultText: 'href' },
|
||||
// { label: 'hreflang', resultText: 'hreflang' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try value suggestion for a tag-specific attribute
|
||||
// testCompletionFor('<ion-side-menu side="|', {
|
||||
// items: [
|
||||
// { label: 'left', resultText: 'left' },
|
||||
// { label: 'primary', resultText: 'primary' },
|
||||
// { label: 'right', resultText: 'right' },
|
||||
// { label: 'secondary', resultText: 'secondary' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try a value suggestion for a global attribute
|
||||
// testCompletionFor('<img force-refresh-images="|', {
|
||||
// items: [
|
||||
// { label: 'false', resultText: 'false' },
|
||||
// { label: 'true', resultText: 'true' },
|
||||
// ]
|
||||
// }),
|
||||
// // Try a value suggestion for an extended attribute of an existing HTML 5 tag
|
||||
// testCompletionFor('<a nav-transition="|', {
|
||||
// items: [
|
||||
// { label: 'android', resultText: 'android' },
|
||||
// { label: 'ios', resultText: 'ios' },
|
||||
// { label: 'none', resultText: 'none' },
|
||||
// ]
|
||||
// })
|
||||
// ], testDone);
|
||||
// });
|
||||
test('Intellisense Ionic', function (testDone): any {
|
||||
run([
|
||||
// Try some Ionic tags
|
||||
testCompletionFor('<|', {
|
||||
items: [
|
||||
{ label: 'ion-checkbox', resultText: '<ion-checkbox' },
|
||||
{ label: 'ion-content', resultText: '<ion-content' },
|
||||
{ label: 'ion-nav-title', resultText: '<ion-nav-title' },
|
||||
]
|
||||
}),
|
||||
testCompletionFor('<ion-re|', {
|
||||
items: [
|
||||
{ label: 'ion-refresher', resultText: '<ion-refresher' },
|
||||
{ label: 'ion-reorder-button', resultText: '<ion-reorder-button' },
|
||||
]
|
||||
}),
|
||||
// Try some global attributes (1 with value suggestions, 1 without value suggestions, 1 void)
|
||||
testCompletionFor('<ion-checkbox |', {
|
||||
items: [
|
||||
{ label: 'force-refresh-images', resultText: '<ion-checkbox force-refresh-images="{{}}"' },
|
||||
{ label: 'collection-repeat', resultText: '<ion-checkbox collection-repeat="{{}}"' },
|
||||
{ label: 'menu-close', resultText: '<ion-checkbox menu-close' },
|
||||
]
|
||||
}),
|
||||
// Try some tag-specific attributes (1 with value suggestions, 1 void)
|
||||
testCompletionFor('<ion-footer-bar |', {
|
||||
items: [
|
||||
{ label: 'align-title', resultText: '<ion-footer-bar align-title="{{}}"' },
|
||||
{ label: 'keyboard-attach', resultText: '<ion-footer-bar keyboard-attach' },
|
||||
]
|
||||
}),
|
||||
// Try the extended attributes of an existing HTML 5 tag
|
||||
testCompletionFor('<a |', {
|
||||
items: [
|
||||
{ label: 'nav-direction', resultText: '<a nav-direction="{{}}"' },
|
||||
{ label: 'nav-transition', resultText: '<a nav-transition="{{}}"' },
|
||||
{ label: 'href', resultText: '<a href="{{}}"' },
|
||||
{ label: 'hreflang', resultText: '<a hreflang="{{}}"' },
|
||||
]
|
||||
}),
|
||||
// Try value suggestion for a tag-specific attribute
|
||||
testCompletionFor('<ion-side-menu side="|', {
|
||||
items: [
|
||||
{ label: 'left', resultText: '<ion-side-menu side="left"' },
|
||||
{ label: 'primary', resultText: '<ion-side-menu side="primary"' },
|
||||
{ label: 'right', resultText: '<ion-side-menu side="right"' },
|
||||
{ label: 'secondary', resultText: '<ion-side-menu side="secondary"' },
|
||||
]
|
||||
}),
|
||||
// Try a value suggestion for a global attribute
|
||||
testCompletionFor('<img force-refresh-images="|', {
|
||||
items: [
|
||||
{ label: 'false', resultText: '<img force-refresh-images="false"' },
|
||||
{ label: 'true', resultText: '<img force-refresh-images="true"' },
|
||||
]
|
||||
}),
|
||||
// Try a value suggestion for an extended attribute of an existing HTML 5 tag
|
||||
testCompletionFor('<a nav-transition="|', {
|
||||
items: [
|
||||
{ label: 'android', resultText: '<a nav-transition="android"' },
|
||||
{ label: 'ios', resultText: '<a nav-transition="ios"' },
|
||||
{ label: 'none', resultText: '<a nav-transition="none"' },
|
||||
]
|
||||
})
|
||||
], testDone);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -10,7 +10,7 @@ import {Node, HTMLDocument, parse} from '../parser/htmlParser';
|
|||
suite('HTML Parser', () => {
|
||||
|
||||
function toJSON(node: Node): any {
|
||||
return { tag: node.tag, start: node.start, end: node.end, children: node.children.map(toJSON) };
|
||||
return { tag: node.tag, start: node.start, end: node.end, closed: node.closed, children: node.children.map(toJSON) };
|
||||
}
|
||||
|
||||
function assertDocument(input: string, expected: any) {
|
||||
|
@ -21,33 +21,34 @@ suite('HTML Parser', () => {
|
|||
function assertNodeBefore(input: string, offset: number, expectedTag: string) {
|
||||
let document = parse(input);
|
||||
let node = document.findNodeBefore(offset);
|
||||
assert.equal(node ? node.tag : '', expectedTag);
|
||||
assert.equal(node ? node.tag : '', expectedTag, "offset " + offset);
|
||||
}
|
||||
|
||||
test('Simple', () => {
|
||||
assertDocument('<html></html>', [ { tag: 'html', start: 0, end: 13, children: []}] );
|
||||
assertDocument('<html><body></body></html>', [ { tag: 'html', start: 0, end: 26, children: [ { tag: 'body', start: 6, end: 19, children: [] }]}] );
|
||||
assertDocument('<html><head></head><body></body></html>', [ { tag: 'html', start: 0, end: 39, children: [ { tag: 'head', start: 6, end: 19, children: [] }, { tag: 'body', start: 19, end: 32, children: [] }]}] );
|
||||
assertDocument('<html></html>', [ { tag: 'html', start: 0, end: 13, closed: true, children: []}] );
|
||||
assertDocument('<html><body></body></html>', [ { tag: 'html', start: 0, end: 26, closed: true, children: [ { tag: 'body', start: 6, end: 19, closed: true, children: [] }]}] );
|
||||
assertDocument('<html><head></head><body></body></html>', [ { tag: 'html', start: 0, end: 39, closed: true, children: [ { tag: 'head', start: 6, end: 19, closed: true, children: [] }, { tag: 'body', start: 19, end: 32, closed: true, children: [] }]}] );
|
||||
});
|
||||
|
||||
test('SelfClose', () => {
|
||||
assertDocument('<br/>', [ { tag: 'br', start: 0, end: 5, children: []}] );
|
||||
assertDocument('<div><br/><span></span></div>', [ { tag: 'div', start: 0, end: 29, children: [{ tag: 'br', start: 5, end: 10, children: [] }, { tag: 'span', start: 10, end: 23, children: [] }]}] );
|
||||
assertDocument('<br/>', [ { tag: 'br', start: 0, end: 5, closed: true, children: []}] );
|
||||
assertDocument('<div><br/><span></span></div>', [ { tag: 'div', start: 0, end: 29, closed: true, children: [{ tag: 'br', start: 5, end: 10, closed: true, children: [] }, { tag: 'span', start: 10, end: 23, closed: true, children: [] }]}] );
|
||||
});
|
||||
|
||||
test('EmptyTag', () => {
|
||||
assertDocument('<meta>', [ { tag: 'meta', start: 0, end: 6, children: []}] );
|
||||
assertDocument('<div><input type="button"><span><br><br></span></div>', [ { tag: 'div', start: 0, end: 53, children: [
|
||||
{ tag: 'input', start: 5, end: 26, children: [] },
|
||||
{ tag: 'span', start: 26, end: 47, children: [{ tag: 'br', start: 32, end: 36, children: [] }, { tag: 'br', start: 36, end: 40, children: [] }] }
|
||||
assertDocument('<meta>', [ { tag: 'meta', start: 0, end: 6, closed: true, children: []}] );
|
||||
assertDocument('<div><input type="button"><span><br><br></span></div>', [ { tag: 'div', start: 0, end: 53, closed: true, children: [
|
||||
{ tag: 'input', start: 5, end: 26, closed: true, children: [] },
|
||||
{ tag: 'span', start: 26, end: 47, closed: true, children: [{ tag: 'br', start: 32, end: 36, closed: true, children: [] }, { tag: 'br', start: 36, end: 40, closed: true, children: [] }] }
|
||||
]}] );
|
||||
});
|
||||
test('MissingTags', () => {
|
||||
assertDocument('</meta>', [] );
|
||||
assertDocument('<div></div></div>', [ { tag: 'div', start: 0, end: 11, children: [] }] );
|
||||
assertDocument('<div><div></div>', [ { tag: 'div', start: 0, end: 16, children: [ { tag: 'div', start: 5, end: 16, children: [] } ] }] );
|
||||
assertDocument('<title><div></title>', [ { tag: 'title', start: 0, end: 20, children: [ { tag: 'div', start: 7, end: 12, children: [] } ] }] );
|
||||
});
|
||||
assertDocument('<div></div></div>', [ { tag: 'div', start: 0, end: 11, closed: true, children: [] }] );
|
||||
assertDocument('<div><div></div>', [ { tag: 'div', start: 0, end: 16, closed: false, children: [ { tag: 'div', start: 5, end: 16, closed: true, children: [] } ] }] );
|
||||
assertDocument('<title><div></title>', [ { tag: 'title', start: 0, end: 20, closed: true, children: [ { tag: 'div', start: 7, end: 12, closed: false, children: [] } ] }] );
|
||||
assertDocument('<h1><div><span></h1>', [ { tag: 'h1', start: 0, end: 20, closed: true, children: [ { tag: 'div', start: 4, end: 15, closed: false, children: [ { tag: 'span', start: 9, end: 15, closed: false, children: [] }] } ] }] );
|
||||
});
|
||||
|
||||
|
||||
test('FindNodeBefore', () => {
|
||||
|
@ -65,11 +66,18 @@ suite('HTML Parser', () => {
|
|||
assertNodeBefore(str, 37, 'hr');
|
||||
assertNodeBefore(str, 40, 'hr');
|
||||
assertNodeBefore(str, 41, 'hr');
|
||||
assertNodeBefore(str, 41, 'hr');
|
||||
assertNodeBefore(str, 42, 'hr');
|
||||
assertNodeBefore(str, 47, 'span');
|
||||
assertNodeBefore(str, 48, 'span');
|
||||
assertNodeBefore(str, 52, 'span');
|
||||
assertNodeBefore(str, 53, 'div');
|
||||
});
|
||||
|
||||
test('FindNodeBefore - incomplete node', () => {
|
||||
let str = '<div><span><br></div>';
|
||||
assertNodeBefore(str, 15, 'br');
|
||||
assertNodeBefore(str, 18, 'br');
|
||||
assertNodeBefore(str, 21, 'div');
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in a new issue