chore: compute aria text consistently with the role accumulated text (#33157)

This commit is contained in:
Pavel Feldman 2024-10-17 17:06:18 -07:00 committed by GitHub
parent 623a8916f9
commit 29c84a33c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1094 additions and 124 deletions

View File

@ -40,8 +40,12 @@ export function parseAriaSnapshot(text: string): AriaTemplateNode {
return { role };
};
const normalizeWhitespace = (text: string) => {
return text.replace(/[\r\n\s\t]+/g, ' ').trim();
};
const valueOrRegex = (value: string): string | RegExp => {
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value;
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : normalizeWhitespace(value);
};
const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => {

View File

@ -15,13 +15,13 @@
*/
import { escapeWithQuotes } from '@isomorphic/stringUtils';
import { beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, isElementIgnoredForAria } from './roleUtils';
import { isElementVisible, isElementStyleVisibilityVisible } from './domUtils';
import { accumulatedElementText, beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, getPseudoContent, isElementIgnoredForAria } from './roleUtils';
import { isElementVisible, isElementStyleVisibilityVisible, getElementComputedStyle } from './domUtils';
type AriaNode = {
role: string;
name?: string;
children?: (AriaNode | string)[];
children: (AriaNode | string)[];
};
export type AriaTemplateNode = {
@ -38,16 +38,20 @@ export function generateAriaTree(rootElement: Element): AriaNode {
const name = role ? getElementAccessibleName(element, false) || undefined : undefined;
const isLeaf = leafRoles.has(role);
const result: AriaNode = { role, name };
if (isLeaf && !name && element.textContent)
result.children = [element.textContent];
const result: AriaNode = { role, name, children: [] };
if (isLeaf && !name) {
const text = accumulatedElementText(element);
if (text)
result.children = [text];
}
return { isLeaf, ariaNode: result };
};
const visit = (ariaNode: AriaNode, node: Node) => {
if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
ariaNode.children = ariaNode.children || [];
ariaNode.children.push(node.nodeValue);
const text = node.nodeValue;
if (text)
ariaNode.children.push(node.nodeValue || '');
return;
}
@ -67,10 +71,8 @@ export function generateAriaTree(rootElement: Element): AriaNode {
if (visible) {
const childAriaNode = toAriaNode(element);
const isHiddenContainer = childAriaNode && hiddenContainerRoles.has(childAriaNode.ariaNode.role);
if (childAriaNode && !isHiddenContainer) {
ariaNode.children = ariaNode.children || [];
if (childAriaNode && !isHiddenContainer)
ariaNode.children.push(childAriaNode.ariaNode);
}
if (isHiddenContainer || !childAriaNode?.isLeaf)
processChildNodes(childAriaNode?.ariaNode || ariaNode, element);
} else {
@ -79,18 +81,36 @@ export function generateAriaTree(rootElement: Element): AriaNode {
};
function processChildNodes(ariaNode: AriaNode, element: Element) {
// Process light DOM children
for (let child = element.firstChild; child; child = child.nextSibling)
visit(ariaNode, child);
// Process shadow DOM children, if any
if (element.shadowRoot) {
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
// Surround every element with spaces for the sake of concatenated text nodes.
const display = getElementComputedStyle(element)?.display || 'inline';
const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : '';
if (treatAsBlock)
ariaNode.children.push(treatAsBlock);
ariaNode.children.push(getPseudoContent(element, '::before'));
const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
if (assignedNodes.length) {
for (const child of assignedNodes)
visit(ariaNode, child);
} else {
for (let child = element.firstChild; child; child = child.nextSibling) {
if (!(child as Element | Text).assignedSlot)
visit(ariaNode, child);
}
if (element.shadowRoot) {
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
visit(ariaNode, child);
}
}
ariaNode.children.push(getPseudoContent(element, '::after'));
if (treatAsBlock)
ariaNode.children.push(treatAsBlock);
}
beginAriaCaches();
const ariaRoot: AriaNode = { role: '' };
const ariaRoot: AriaNode = { role: '', children: [] };
try {
visit(ariaRoot, rootElement);
} finally {
@ -128,7 +148,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) {
}
}
flushChildren(buffer, normalizedChildren);
ariaNode.children = normalizedChildren.length ? normalizedChildren : undefined;
ariaNode.children = normalizedChildren.length ? normalizedChildren : [];
};
visit(rootA11yNode);
}
@ -144,7 +164,7 @@ const leafRoles = new Set([
'textbox', 'time', 'tooltip'
]);
const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\n]+/g, ' ');
const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' ');
function matchesText(text: string | undefined, template: RegExp | string | undefined) {
if (!template)
@ -233,7 +253,7 @@ export function renderAriaTree(ariaNode: AriaNode): string {
lines.push(line);
return;
}
lines.push(line + (ariaNode.children ? ':' : ''));
lines.push(line + (ariaNode.children.length ? ':' : ''));
for (const child of ariaNode.children || [])
visit(child, indent + ' ');
};

View File

@ -363,7 +363,7 @@ function queryInAriaOwned(element: Element, selector: string): Element[] {
return result;
}
function getPseudoContent(element: Element, pseudo: '::before' | '::after') {
export function getPseudoContent(element: Element, pseudo: '::before' | '::after') {
const cache = pseudo === '::before' ? cachePseudoContentBefore : cachePseudoContentAfter;
if (cache?.has(element))
return cache?.get(element) || '';
@ -430,10 +430,6 @@ export function getElementAccessibleName(element: Element, includeHidden: boolea
accessibleName = asFlatString(getTextAlternativeInternal(element, {
includeHidden,
visitedElements: new Set(),
embeddedInDescribedBy: undefined,
embeddedInLabelledBy: undefined,
embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined,
embeddedInTargetElement: 'self',
}));
}
@ -458,10 +454,6 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
accessibleDescription = asFlatString(describedBy.map(ref => getTextAlternativeInternal(ref, {
includeHidden,
visitedElements: new Set(),
embeddedInLabelledBy: undefined,
embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined,
embeddedInTargetElement: 'none',
embeddedInDescribedBy: { element: ref, hidden: isElementHiddenForAria(ref) },
})).join(' '));
} else if (element.hasAttribute('aria-description')) {
@ -480,13 +472,13 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
}
type AccessibleNameOptions = {
includeHidden: boolean,
visitedElements: Set<Element>,
embeddedInDescribedBy: { element: Element, hidden: boolean } | undefined,
embeddedInLabelledBy: { element: Element, hidden: boolean } | undefined,
embeddedInLabel: { element: Element, hidden: boolean } | undefined,
embeddedInNativeTextAlternative: { element: Element, hidden: boolean } | undefined,
embeddedInTargetElement: 'none' | 'self' | 'descendant',
includeHidden?: boolean,
embeddedInDescribedBy?: { element: Element, hidden: boolean },
embeddedInLabelledBy?: { element: Element, hidden: boolean },
embeddedInLabel?: { element: Element, hidden: boolean },
embeddedInNativeTextAlternative?: { element: Element, hidden: boolean },
embeddedInTargetElement?: 'self' | 'descendant',
};
function getTextAlternativeInternal(element: Element, options: AccessibleNameOptions): string {
@ -525,7 +517,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
...options,
embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
embeddedInDescribedBy: undefined,
embeddedInTargetElement: 'none',
embeddedInTargetElement: undefined,
embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined,
})).join(' ');
@ -778,42 +770,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
!!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy ||
!!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) {
options.visitedElements.add(element);
const tokens: string[] = [];
const visit = (node: Node, skipSlotted: boolean) => {
if (skipSlotted && (node as Element | Text).assignedSlot)
return;
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
const display = getElementComputedStyle(node as Element)?.display || 'inline';
let token = getTextAlternativeInternal(node as Element, childOptions);
// SPEC DIFFERENCE.
// Spec says "append the result to the accumulated text", assuming "with space".
// However, multiple tests insist that inline elements do not add a space.
// Additionally, <br> insists on a space anyway, see "name_file-label-inline-block-elements-manual.html"
if (display !== 'inline' || node.nodeName === 'BR')
token = ' ' + token + ' ';
tokens.push(token);
} else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
// step 2g.
tokens.push(node.textContent || '');
}
};
tokens.push(getPseudoContent(element, '::before'));
const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
if (assignedNodes.length) {
for (const child of assignedNodes)
visit(child, false);
} else {
for (let child = element.firstChild; child; child = child.nextSibling)
visit(child, true);
if (element.shadowRoot) {
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
visit(child, true);
}
for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
visit(owned, true);
}
tokens.push(getPseudoContent(element, '::after'));
const accessibleName = tokens.join('');
const accessibleName = innerAccumulatedElementText(element, childOptions);
// Spec says "Return the accumulated text if it is not the empty string". However, that is not really
// compatible with the real browser behavior and wpt tests, where an element with empty contents will fallback to the title.
// So we follow the spec everywhere except for the target element itself. This can probably be improved.
@ -834,6 +791,50 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
return '';
}
function innerAccumulatedElementText(element: Element, options: AccessibleNameOptions): string {
const tokens: string[] = [];
const visit = (node: Node, skipSlotted: boolean) => {
if (skipSlotted && (node as Element | Text).assignedSlot)
return;
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
const display = getElementComputedStyle(node as Element)?.display || 'inline';
let token = getTextAlternativeInternal(node as Element, options);
// SPEC DIFFERENCE.
// Spec says "append the result to the accumulated text", assuming "with space".
// However, multiple tests insist that inline elements do not add a space.
// Additionally, <br> insists on a space anyway, see "name_file-label-inline-block-elements-manual.html"
if (display !== 'inline' || node.nodeName === 'BR')
token = ' ' + token + ' ';
tokens.push(token);
} else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
// step 2g.
tokens.push(node.textContent || '');
}
};
tokens.push(getPseudoContent(element, '::before'));
const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
if (assignedNodes.length) {
for (const child of assignedNodes)
visit(child, false);
} else {
for (let child = element.firstChild; child; child = child.nextSibling)
visit(child, true);
if (element.shadowRoot) {
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
visit(child, true);
}
for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
visit(owned, true);
}
tokens.push(getPseudoContent(element, '::after'));
return tokens.join('');
}
export function accumulatedElementText(element: Element): string {
const visitedElements = new Set<Element>();
return asFlatString(innerAccumulatedElementText(element, { visitedElements })).trim();
}
export const kAriaSelectedRoles = ['gridcell', 'option', 'row', 'tab', 'rowheader', 'columnheader', 'treeitem'];
export function getAriaSelected(element: Element): boolean {
// https://www.w3.org/TR/wai-aria-1.2/#aria-selected
@ -958,7 +959,7 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement
embeddedInNativeTextAlternative: undefined,
embeddedInLabelledBy: undefined,
embeddedInDescribedBy: undefined,
embeddedInTargetElement: 'none',
embeddedInTargetElement: undefined,
})).filter(accessibleName => !!accessibleName).join(' ');
}

View File

@ -49,17 +49,19 @@ export async function toMatchAriaSnapshot(
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
const notFound = received === kNoElementsFoundError;
const escapedExpected = escapePrivateUsePoints(expected);
const escapedReceived = escapePrivateUsePoints(received);
const message = () => {
if (pass) {
if (notFound)
return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
const printedReceived = printReceivedStringContainExpectedSubstring(received, received.indexOf(expected), expected.length);
return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived string: ${printedReceived}` + callLogText(log);
return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
const printedReceived = printReceivedStringContainExpectedSubstring(escapedReceived, escapedReceived.indexOf(escapedExpected), escapedExpected.length);
return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived string: ${printedReceived}` + callLogText(log);
} else {
const labelExpected = `Expected`;
if (notFound)
return messagePrefix + `${labelExpected}: ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
return messagePrefix + this.utils.printDiffOrStringify(expected, received, labelExpected, 'Received string', false) + callLogText(log);
return messagePrefix + `${labelExpected}: ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
return messagePrefix + this.utils.printDiffOrStringify(escapedExpected, escapedReceived, labelExpected, 'Received string', false) + callLogText(log);
}
};
@ -73,3 +75,7 @@ export async function toMatchAriaSnapshot(
timeout: timedOut ? timeout : undefined,
};
}
function escapePrivateUsePoints(str: string) {
return str.replace(/[\uE000-\uF8FF]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
}

596
tests/assets/codicon.css Normal file
View File

@ -0,0 +1,596 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
@font-face {
font-family: "codicon";
src: url("codicon.ttf") format("truetype");
}
.codicon {
font: normal normal normal 16px/1 codicon;
flex: none;
display: inline-block;
text-decoration: none;
text-rendering: auto;
text-align: center;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.codicon-add:before { content: '\ea60'; }
.codicon-plus:before { content: '\ea60'; }
.codicon-gist-new:before { content: '\ea60'; }
.codicon-repo-create:before { content: '\ea60'; }
.codicon-lightbulb:before { content: '\ea61'; }
.codicon-light-bulb:before { content: '\ea61'; }
.codicon-repo:before { content: '\ea62'; }
.codicon-repo-delete:before { content: '\ea62'; }
.codicon-gist-fork:before { content: '\ea63'; }
.codicon-repo-forked:before { content: '\ea63'; }
.codicon-git-pull-request:before { content: '\ea64'; }
.codicon-git-pull-request-abandoned:before { content: '\ea64'; }
.codicon-record-keys:before { content: '\ea65'; }
.codicon-keyboard:before { content: '\ea65'; }
.codicon-tag:before { content: '\ea66'; }
.codicon-git-pull-request-label:before { content: '\ea66'; }
.codicon-tag-add:before { content: '\ea66'; }
.codicon-tag-remove:before { content: '\ea66'; }
.codicon-person:before { content: '\ea67'; }
.codicon-person-follow:before { content: '\ea67'; }
.codicon-person-outline:before { content: '\ea67'; }
.codicon-person-filled:before { content: '\ea67'; }
.codicon-git-branch:before { content: '\ea68'; }
.codicon-git-branch-create:before { content: '\ea68'; }
.codicon-git-branch-delete:before { content: '\ea68'; }
.codicon-source-control:before { content: '\ea68'; }
.codicon-mirror:before { content: '\ea69'; }
.codicon-mirror-public:before { content: '\ea69'; }
.codicon-star:before { content: '\ea6a'; }
.codicon-star-add:before { content: '\ea6a'; }
.codicon-star-delete:before { content: '\ea6a'; }
.codicon-star-empty:before { content: '\ea6a'; }
.codicon-comment:before { content: '\ea6b'; }
.codicon-comment-add:before { content: '\ea6b'; }
.codicon-alert:before { content: '\ea6c'; }
.codicon-warning:before { content: '\ea6c'; }
.codicon-search:before { content: '\ea6d'; }
.codicon-search-save:before { content: '\ea6d'; }
.codicon-log-out:before { content: '\ea6e'; }
.codicon-sign-out:before { content: '\ea6e'; }
.codicon-log-in:before { content: '\ea6f'; }
.codicon-sign-in:before { content: '\ea6f'; }
.codicon-eye:before { content: '\ea70'; }
.codicon-eye-unwatch:before { content: '\ea70'; }
.codicon-eye-watch:before { content: '\ea70'; }
.codicon-circle-filled:before { content: '\ea71'; }
.codicon-primitive-dot:before { content: '\ea71'; }
.codicon-close-dirty:before { content: '\ea71'; }
.codicon-debug-breakpoint:before { content: '\ea71'; }
.codicon-debug-breakpoint-disabled:before { content: '\ea71'; }
.codicon-debug-hint:before { content: '\ea71'; }
.codicon-terminal-decoration-success:before { content: '\ea71'; }
.codicon-primitive-square:before { content: '\ea72'; }
.codicon-edit:before { content: '\ea73'; }
.codicon-pencil:before { content: '\ea73'; }
.codicon-info:before { content: '\ea74'; }
.codicon-issue-opened:before { content: '\ea74'; }
.codicon-gist-private:before { content: '\ea75'; }
.codicon-git-fork-private:before { content: '\ea75'; }
.codicon-lock:before { content: '\ea75'; }
.codicon-mirror-private:before { content: '\ea75'; }
.codicon-close:before { content: '\ea76'; }
.codicon-remove-close:before { content: '\ea76'; }
.codicon-x:before { content: '\ea76'; }
.codicon-repo-sync:before { content: '\ea77'; }
.codicon-sync:before { content: '\ea77'; }
.codicon-clone:before { content: '\ea78'; }
.codicon-desktop-download:before { content: '\ea78'; }
.codicon-beaker:before { content: '\ea79'; }
.codicon-microscope:before { content: '\ea79'; }
.codicon-vm:before { content: '\ea7a'; }
.codicon-device-desktop:before { content: '\ea7a'; }
.codicon-file:before { content: '\ea7b'; }
.codicon-file-text:before { content: '\ea7b'; }
.codicon-more:before { content: '\ea7c'; }
.codicon-ellipsis:before { content: '\ea7c'; }
.codicon-kebab-horizontal:before { content: '\ea7c'; }
.codicon-mail-reply:before { content: '\ea7d'; }
.codicon-reply:before { content: '\ea7d'; }
.codicon-organization:before { content: '\ea7e'; }
.codicon-organization-filled:before { content: '\ea7e'; }
.codicon-organization-outline:before { content: '\ea7e'; }
.codicon-new-file:before { content: '\ea7f'; }
.codicon-file-add:before { content: '\ea7f'; }
.codicon-new-folder:before { content: '\ea80'; }
.codicon-file-directory-create:before { content: '\ea80'; }
.codicon-trash:before { content: '\ea81'; }
.codicon-trashcan:before { content: '\ea81'; }
.codicon-history:before { content: '\ea82'; }
.codicon-clock:before { content: '\ea82'; }
.codicon-folder:before { content: '\ea83'; }
.codicon-file-directory:before { content: '\ea83'; }
.codicon-symbol-folder:before { content: '\ea83'; }
.codicon-logo-github:before { content: '\ea84'; }
.codicon-mark-github:before { content: '\ea84'; }
.codicon-github:before { content: '\ea84'; }
.codicon-terminal:before { content: '\ea85'; }
.codicon-console:before { content: '\ea85'; }
.codicon-repl:before { content: '\ea85'; }
.codicon-zap:before { content: '\ea86'; }
.codicon-symbol-event:before { content: '\ea86'; }
.codicon-error:before { content: '\ea87'; }
.codicon-stop:before { content: '\ea87'; }
.codicon-variable:before { content: '\ea88'; }
.codicon-symbol-variable:before { content: '\ea88'; }
.codicon-array:before { content: '\ea8a'; }
.codicon-symbol-array:before { content: '\ea8a'; }
.codicon-symbol-module:before { content: '\ea8b'; }
.codicon-symbol-package:before { content: '\ea8b'; }
.codicon-symbol-namespace:before { content: '\ea8b'; }
.codicon-symbol-object:before { content: '\ea8b'; }
.codicon-symbol-method:before { content: '\ea8c'; }
.codicon-symbol-function:before { content: '\ea8c'; }
.codicon-symbol-constructor:before { content: '\ea8c'; }
.codicon-symbol-boolean:before { content: '\ea8f'; }
.codicon-symbol-null:before { content: '\ea8f'; }
.codicon-symbol-numeric:before { content: '\ea90'; }
.codicon-symbol-number:before { content: '\ea90'; }
.codicon-symbol-structure:before { content: '\ea91'; }
.codicon-symbol-struct:before { content: '\ea91'; }
.codicon-symbol-parameter:before { content: '\ea92'; }
.codicon-symbol-type-parameter:before { content: '\ea92'; }
.codicon-symbol-key:before { content: '\ea93'; }
.codicon-symbol-text:before { content: '\ea93'; }
.codicon-symbol-reference:before { content: '\ea94'; }
.codicon-go-to-file:before { content: '\ea94'; }
.codicon-symbol-enum:before { content: '\ea95'; }
.codicon-symbol-value:before { content: '\ea95'; }
.codicon-symbol-ruler:before { content: '\ea96'; }
.codicon-symbol-unit:before { content: '\ea96'; }
.codicon-activate-breakpoints:before { content: '\ea97'; }
.codicon-archive:before { content: '\ea98'; }
.codicon-arrow-both:before { content: '\ea99'; }
.codicon-arrow-down:before { content: '\ea9a'; }
.codicon-arrow-left:before { content: '\ea9b'; }
.codicon-arrow-right:before { content: '\ea9c'; }
.codicon-arrow-small-down:before { content: '\ea9d'; }
.codicon-arrow-small-left:before { content: '\ea9e'; }
.codicon-arrow-small-right:before { content: '\ea9f'; }
.codicon-arrow-small-up:before { content: '\eaa0'; }
.codicon-arrow-up:before { content: '\eaa1'; }
.codicon-bell:before { content: '\eaa2'; }
.codicon-bold:before { content: '\eaa3'; }
.codicon-book:before { content: '\eaa4'; }
.codicon-bookmark:before { content: '\eaa5'; }
.codicon-debug-breakpoint-conditional-unverified:before { content: '\eaa6'; }
.codicon-debug-breakpoint-conditional:before { content: '\eaa7'; }
.codicon-debug-breakpoint-conditional-disabled:before { content: '\eaa7'; }
.codicon-debug-breakpoint-data-unverified:before { content: '\eaa8'; }
.codicon-debug-breakpoint-data:before { content: '\eaa9'; }
.codicon-debug-breakpoint-data-disabled:before { content: '\eaa9'; }
.codicon-debug-breakpoint-log-unverified:before { content: '\eaaa'; }
.codicon-debug-breakpoint-log:before { content: '\eaab'; }
.codicon-debug-breakpoint-log-disabled:before { content: '\eaab'; }
.codicon-briefcase:before { content: '\eaac'; }
.codicon-broadcast:before { content: '\eaad'; }
.codicon-browser:before { content: '\eaae'; }
.codicon-bug:before { content: '\eaaf'; }
.codicon-calendar:before { content: '\eab0'; }
.codicon-case-sensitive:before { content: '\eab1'; }
.codicon-check:before { content: '\eab2'; }
.codicon-checklist:before { content: '\eab3'; }
.codicon-chevron-down:before { content: '\eab4'; }
.codicon-chevron-left:before { content: '\eab5'; }
.codicon-chevron-right:before { content: '\eab6'; }
.codicon-chevron-up:before { content: '\eab7'; }
.codicon-chrome-close:before { content: '\eab8'; }
.codicon-chrome-maximize:before { content: '\eab9'; }
.codicon-chrome-minimize:before { content: '\eaba'; }
.codicon-chrome-restore:before { content: '\eabb'; }
.codicon-circle-outline:before { content: '\eabc'; }
.codicon-circle:before { content: '\eabc'; }
.codicon-debug-breakpoint-unverified:before { content: '\eabc'; }
.codicon-terminal-decoration-incomplete:before { content: '\eabc'; }
.codicon-circle-slash:before { content: '\eabd'; }
.codicon-circuit-board:before { content: '\eabe'; }
.codicon-clear-all:before { content: '\eabf'; }
.codicon-clippy:before { content: '\eac0'; }
.codicon-close-all:before { content: '\eac1'; }
.codicon-cloud-download:before { content: '\eac2'; }
.codicon-cloud-upload:before { content: '\eac3'; }
.codicon-code:before { content: '\eac4'; }
.codicon-collapse-all:before { content: '\eac5'; }
.codicon-color-mode:before { content: '\eac6'; }
.codicon-comment-discussion:before { content: '\eac7'; }
.codicon-credit-card:before { content: '\eac9'; }
.codicon-dash:before { content: '\eacc'; }
.codicon-dashboard:before { content: '\eacd'; }
.codicon-database:before { content: '\eace'; }
.codicon-debug-continue:before { content: '\eacf'; }
.codicon-debug-disconnect:before { content: '\ead0'; }
.codicon-debug-pause:before { content: '\ead1'; }
.codicon-debug-restart:before { content: '\ead2'; }
.codicon-debug-start:before { content: '\ead3'; }
.codicon-debug-step-into:before { content: '\ead4'; }
.codicon-debug-step-out:before { content: '\ead5'; }
.codicon-debug-step-over:before { content: '\ead6'; }
.codicon-debug-stop:before { content: '\ead7'; }
.codicon-debug:before { content: '\ead8'; }
.codicon-device-camera-video:before { content: '\ead9'; }
.codicon-device-camera:before { content: '\eada'; }
.codicon-device-mobile:before { content: '\eadb'; }
.codicon-diff-added:before { content: '\eadc'; }
.codicon-diff-ignored:before { content: '\eadd'; }
.codicon-diff-modified:before { content: '\eade'; }
.codicon-diff-removed:before { content: '\eadf'; }
.codicon-diff-renamed:before { content: '\eae0'; }
.codicon-diff:before { content: '\eae1'; }
.codicon-diff-sidebyside:before { content: '\eae1'; }
.codicon-discard:before { content: '\eae2'; }
.codicon-editor-layout:before { content: '\eae3'; }
.codicon-empty-window:before { content: '\eae4'; }
.codicon-exclude:before { content: '\eae5'; }
.codicon-extensions:before { content: '\eae6'; }
.codicon-eye-closed:before { content: '\eae7'; }
.codicon-file-binary:before { content: '\eae8'; }
.codicon-file-code:before { content: '\eae9'; }
.codicon-file-media:before { content: '\eaea'; }
.codicon-file-pdf:before { content: '\eaeb'; }
.codicon-file-submodule:before { content: '\eaec'; }
.codicon-file-symlink-directory:before { content: '\eaed'; }
.codicon-file-symlink-file:before { content: '\eaee'; }
.codicon-file-zip:before { content: '\eaef'; }
.codicon-files:before { content: '\eaf0'; }
.codicon-filter:before { content: '\eaf1'; }
.codicon-flame:before { content: '\eaf2'; }
.codicon-fold-down:before { content: '\eaf3'; }
.codicon-fold-up:before { content: '\eaf4'; }
.codicon-fold:before { content: '\eaf5'; }
.codicon-folder-active:before { content: '\eaf6'; }
.codicon-folder-opened:before { content: '\eaf7'; }
.codicon-gear:before { content: '\eaf8'; }
.codicon-gift:before { content: '\eaf9'; }
.codicon-gist-secret:before { content: '\eafa'; }
.codicon-gist:before { content: '\eafb'; }
.codicon-git-commit:before { content: '\eafc'; }
.codicon-git-compare:before { content: '\eafd'; }
.codicon-compare-changes:before { content: '\eafd'; }
.codicon-git-merge:before { content: '\eafe'; }
.codicon-github-action:before { content: '\eaff'; }
.codicon-github-alt:before { content: '\eb00'; }
.codicon-globe:before { content: '\eb01'; }
.codicon-grabber:before { content: '\eb02'; }
.codicon-graph:before { content: '\eb03'; }
.codicon-gripper:before { content: '\eb04'; }
.codicon-heart:before { content: '\eb05'; }
.codicon-home:before { content: '\eb06'; }
.codicon-horizontal-rule:before { content: '\eb07'; }
.codicon-hubot:before { content: '\eb08'; }
.codicon-inbox:before { content: '\eb09'; }
.codicon-issue-reopened:before { content: '\eb0b'; }
.codicon-issues:before { content: '\eb0c'; }
.codicon-italic:before { content: '\eb0d'; }
.codicon-jersey:before { content: '\eb0e'; }
.codicon-json:before { content: '\eb0f'; }
.codicon-kebab-vertical:before { content: '\eb10'; }
.codicon-key:before { content: '\eb11'; }
.codicon-law:before { content: '\eb12'; }
.codicon-lightbulb-autofix:before { content: '\eb13'; }
.codicon-link-external:before { content: '\eb14'; }
.codicon-link:before { content: '\eb15'; }
.codicon-list-ordered:before { content: '\eb16'; }
.codicon-list-unordered:before { content: '\eb17'; }
.codicon-live-share:before { content: '\eb18'; }
.codicon-loading:before { content: '\eb19'; }
.codicon-location:before { content: '\eb1a'; }
.codicon-mail-read:before { content: '\eb1b'; }
.codicon-mail:before { content: '\eb1c'; }
.codicon-markdown:before { content: '\eb1d'; }
.codicon-megaphone:before { content: '\eb1e'; }
.codicon-mention:before { content: '\eb1f'; }
.codicon-milestone:before { content: '\eb20'; }
.codicon-git-pull-request-milestone:before { content: '\eb20'; }
.codicon-mortar-board:before { content: '\eb21'; }
.codicon-move:before { content: '\eb22'; }
.codicon-multiple-windows:before { content: '\eb23'; }
.codicon-mute:before { content: '\eb24'; }
.codicon-no-newline:before { content: '\eb25'; }
.codicon-note:before { content: '\eb26'; }
.codicon-octoface:before { content: '\eb27'; }
.codicon-open-preview:before { content: '\eb28'; }
.codicon-package:before { content: '\eb29'; }
.codicon-paintcan:before { content: '\eb2a'; }
.codicon-pin:before { content: '\eb2b'; }
.codicon-play:before { content: '\eb2c'; }
.codicon-run:before { content: '\eb2c'; }
.codicon-plug:before { content: '\eb2d'; }
.codicon-preserve-case:before { content: '\eb2e'; }
.codicon-preview:before { content: '\eb2f'; }
.codicon-project:before { content: '\eb30'; }
.codicon-pulse:before { content: '\eb31'; }
.codicon-question:before { content: '\eb32'; }
.codicon-quote:before { content: '\eb33'; }
.codicon-radio-tower:before { content: '\eb34'; }
.codicon-reactions:before { content: '\eb35'; }
.codicon-references:before { content: '\eb36'; }
.codicon-refresh:before { content: '\eb37'; }
.codicon-regex:before { content: '\eb38'; }
.codicon-remote-explorer:before { content: '\eb39'; }
.codicon-remote:before { content: '\eb3a'; }
.codicon-remove:before { content: '\eb3b'; }
.codicon-replace-all:before { content: '\eb3c'; }
.codicon-replace:before { content: '\eb3d'; }
.codicon-repo-clone:before { content: '\eb3e'; }
.codicon-repo-force-push:before { content: '\eb3f'; }
.codicon-repo-pull:before { content: '\eb40'; }
.codicon-repo-push:before { content: '\eb41'; }
.codicon-report:before { content: '\eb42'; }
.codicon-request-changes:before { content: '\eb43'; }
.codicon-rocket:before { content: '\eb44'; }
.codicon-root-folder-opened:before { content: '\eb45'; }
.codicon-root-folder:before { content: '\eb46'; }
.codicon-rss:before { content: '\eb47'; }
.codicon-ruby:before { content: '\eb48'; }
.codicon-save-all:before { content: '\eb49'; }
.codicon-save-as:before { content: '\eb4a'; }
.codicon-save:before { content: '\eb4b'; }
.codicon-screen-full:before { content: '\eb4c'; }
.codicon-screen-normal:before { content: '\eb4d'; }
.codicon-search-stop:before { content: '\eb4e'; }
.codicon-server:before { content: '\eb50'; }
.codicon-settings-gear:before { content: '\eb51'; }
.codicon-settings:before { content: '\eb52'; }
.codicon-shield:before { content: '\eb53'; }
.codicon-smiley:before { content: '\eb54'; }
.codicon-sort-precedence:before { content: '\eb55'; }
.codicon-split-horizontal:before { content: '\eb56'; }
.codicon-split-vertical:before { content: '\eb57'; }
.codicon-squirrel:before { content: '\eb58'; }
.codicon-star-full:before { content: '\eb59'; }
.codicon-star-half:before { content: '\eb5a'; }
.codicon-symbol-class:before { content: '\eb5b'; }
.codicon-symbol-color:before { content: '\eb5c'; }
.codicon-symbol-constant:before { content: '\eb5d'; }
.codicon-symbol-enum-member:before { content: '\eb5e'; }
.codicon-symbol-field:before { content: '\eb5f'; }
.codicon-symbol-file:before { content: '\eb60'; }
.codicon-symbol-interface:before { content: '\eb61'; }
.codicon-symbol-keyword:before { content: '\eb62'; }
.codicon-symbol-misc:before { content: '\eb63'; }
.codicon-symbol-operator:before { content: '\eb64'; }
.codicon-symbol-property:before { content: '\eb65'; }
.codicon-wrench:before { content: '\eb65'; }
.codicon-wrench-subaction:before { content: '\eb65'; }
.codicon-symbol-snippet:before { content: '\eb66'; }
.codicon-tasklist:before { content: '\eb67'; }
.codicon-telescope:before { content: '\eb68'; }
.codicon-text-size:before { content: '\eb69'; }
.codicon-three-bars:before { content: '\eb6a'; }
.codicon-thumbsdown:before { content: '\eb6b'; }
.codicon-thumbsup:before { content: '\eb6c'; }
.codicon-tools:before { content: '\eb6d'; }
.codicon-triangle-down:before { content: '\eb6e'; }
.codicon-triangle-left:before { content: '\eb6f'; }
.codicon-triangle-right:before { content: '\eb70'; }
.codicon-triangle-up:before { content: '\eb71'; }
.codicon-twitter:before { content: '\eb72'; }
.codicon-unfold:before { content: '\eb73'; }
.codicon-unlock:before { content: '\eb74'; }
.codicon-unmute:before { content: '\eb75'; }
.codicon-unverified:before { content: '\eb76'; }
.codicon-verified:before { content: '\eb77'; }
.codicon-versions:before { content: '\eb78'; }
.codicon-vm-active:before { content: '\eb79'; }
.codicon-vm-outline:before { content: '\eb7a'; }
.codicon-vm-running:before { content: '\eb7b'; }
.codicon-watch:before { content: '\eb7c'; }
.codicon-whitespace:before { content: '\eb7d'; }
.codicon-whole-word:before { content: '\eb7e'; }
.codicon-window:before { content: '\eb7f'; }
.codicon-word-wrap:before { content: '\eb80'; }
.codicon-zoom-in:before { content: '\eb81'; }
.codicon-zoom-out:before { content: '\eb82'; }
.codicon-list-filter:before { content: '\eb83'; }
.codicon-list-flat:before { content: '\eb84'; }
.codicon-list-selection:before { content: '\eb85'; }
.codicon-selection:before { content: '\eb85'; }
.codicon-list-tree:before { content: '\eb86'; }
.codicon-debug-breakpoint-function-unverified:before { content: '\eb87'; }
.codicon-debug-breakpoint-function:before { content: '\eb88'; }
.codicon-debug-breakpoint-function-disabled:before { content: '\eb88'; }
.codicon-debug-stackframe-active:before { content: '\eb89'; }
.codicon-circle-small-filled:before { content: '\eb8a'; }
.codicon-debug-stackframe-dot:before { content: '\eb8a'; }
.codicon-terminal-decoration-mark:before { content: '\eb8a'; }
.codicon-debug-stackframe:before { content: '\eb8b'; }
.codicon-debug-stackframe-focused:before { content: '\eb8b'; }
.codicon-debug-breakpoint-unsupported:before { content: '\eb8c'; }
.codicon-symbol-string:before { content: '\eb8d'; }
.codicon-debug-reverse-continue:before { content: '\eb8e'; }
.codicon-debug-step-back:before { content: '\eb8f'; }
.codicon-debug-restart-frame:before { content: '\eb90'; }
.codicon-debug-alt:before { content: '\eb91'; }
.codicon-call-incoming:before { content: '\eb92'; }
.codicon-call-outgoing:before { content: '\eb93'; }
.codicon-menu:before { content: '\eb94'; }
.codicon-expand-all:before { content: '\eb95'; }
.codicon-feedback:before { content: '\eb96'; }
.codicon-git-pull-request-reviewer:before { content: '\eb96'; }
.codicon-group-by-ref-type:before { content: '\eb97'; }
.codicon-ungroup-by-ref-type:before { content: '\eb98'; }
.codicon-account:before { content: '\eb99'; }
.codicon-git-pull-request-assignee:before { content: '\eb99'; }
.codicon-bell-dot:before { content: '\eb9a'; }
.codicon-debug-console:before { content: '\eb9b'; }
.codicon-library:before { content: '\eb9c'; }
.codicon-output:before { content: '\eb9d'; }
.codicon-run-all:before { content: '\eb9e'; }
.codicon-sync-ignored:before { content: '\eb9f'; }
.codicon-pinned:before { content: '\eba0'; }
.codicon-github-inverted:before { content: '\eba1'; }
.codicon-server-process:before { content: '\eba2'; }
.codicon-server-environment:before { content: '\eba3'; }
.codicon-pass:before { content: '\eba4'; }
.codicon-issue-closed:before { content: '\eba4'; }
.codicon-stop-circle:before { content: '\eba5'; }
.codicon-play-circle:before { content: '\eba6'; }
.codicon-record:before { content: '\eba7'; }
.codicon-debug-alt-small:before { content: '\eba8'; }
.codicon-vm-connect:before { content: '\eba9'; }
.codicon-cloud:before { content: '\ebaa'; }
.codicon-merge:before { content: '\ebab'; }
.codicon-export:before { content: '\ebac'; }
.codicon-graph-left:before { content: '\ebad'; }
.codicon-magnet:before { content: '\ebae'; }
.codicon-notebook:before { content: '\ebaf'; }
.codicon-redo:before { content: '\ebb0'; }
.codicon-check-all:before { content: '\ebb1'; }
.codicon-pinned-dirty:before { content: '\ebb2'; }
.codicon-pass-filled:before { content: '\ebb3'; }
.codicon-circle-large-filled:before { content: '\ebb4'; }
.codicon-circle-large:before { content: '\ebb5'; }
.codicon-circle-large-outline:before { content: '\ebb5'; }
.codicon-combine:before { content: '\ebb6'; }
.codicon-gather:before { content: '\ebb6'; }
.codicon-table:before { content: '\ebb7'; }
.codicon-variable-group:before { content: '\ebb8'; }
.codicon-type-hierarchy:before { content: '\ebb9'; }
.codicon-type-hierarchy-sub:before { content: '\ebba'; }
.codicon-type-hierarchy-super:before { content: '\ebbb'; }
.codicon-git-pull-request-create:before { content: '\ebbc'; }
.codicon-run-above:before { content: '\ebbd'; }
.codicon-run-below:before { content: '\ebbe'; }
.codicon-notebook-template:before { content: '\ebbf'; }
.codicon-debug-rerun:before { content: '\ebc0'; }
.codicon-workspace-trusted:before { content: '\ebc1'; }
.codicon-workspace-untrusted:before { content: '\ebc2'; }
.codicon-workspace-unknown:before { content: '\ebc3'; }
.codicon-terminal-cmd:before { content: '\ebc4'; }
.codicon-terminal-debian:before { content: '\ebc5'; }
.codicon-terminal-linux:before { content: '\ebc6'; }
.codicon-terminal-powershell:before { content: '\ebc7'; }
.codicon-terminal-tmux:before { content: '\ebc8'; }
.codicon-terminal-ubuntu:before { content: '\ebc9'; }
.codicon-terminal-bash:before { content: '\ebca'; }
.codicon-arrow-swap:before { content: '\ebcb'; }
.codicon-copy:before { content: '\ebcc'; }
.codicon-person-add:before { content: '\ebcd'; }
.codicon-filter-filled:before { content: '\ebce'; }
.codicon-wand:before { content: '\ebcf'; }
.codicon-debug-line-by-line:before { content: '\ebd0'; }
.codicon-inspect:before { content: '\ebd1'; }
.codicon-layers:before { content: '\ebd2'; }
.codicon-layers-dot:before { content: '\ebd3'; }
.codicon-layers-active:before { content: '\ebd4'; }
.codicon-compass:before { content: '\ebd5'; }
.codicon-compass-dot:before { content: '\ebd6'; }
.codicon-compass-active:before { content: '\ebd7'; }
.codicon-azure:before { content: '\ebd8'; }
.codicon-issue-draft:before { content: '\ebd9'; }
.codicon-git-pull-request-closed:before { content: '\ebda'; }
.codicon-git-pull-request-draft:before { content: '\ebdb'; }
.codicon-debug-all:before { content: '\ebdc'; }
.codicon-debug-coverage:before { content: '\ebdd'; }
.codicon-run-errors:before { content: '\ebde'; }
.codicon-folder-library:before { content: '\ebdf'; }
.codicon-debug-continue-small:before { content: '\ebe0'; }
.codicon-beaker-stop:before { content: '\ebe1'; }
.codicon-graph-line:before { content: '\ebe2'; }
.codicon-graph-scatter:before { content: '\ebe3'; }
.codicon-pie-chart:before { content: '\ebe4'; }
.codicon-bracket:before { content: '\eb0f'; }
.codicon-bracket-dot:before { content: '\ebe5'; }
.codicon-bracket-error:before { content: '\ebe6'; }
.codicon-lock-small:before { content: '\ebe7'; }
.codicon-azure-devops:before { content: '\ebe8'; }
.codicon-verified-filled:before { content: '\ebe9'; }
.codicon-newline:before { content: '\ebea'; }
.codicon-layout:before { content: '\ebeb'; }
.codicon-layout-activitybar-left:before { content: '\ebec'; }
.codicon-layout-activitybar-right:before { content: '\ebed'; }
.codicon-layout-panel-left:before { content: '\ebee'; }
.codicon-layout-panel-center:before { content: '\ebef'; }
.codicon-layout-panel-justify:before { content: '\ebf0'; }
.codicon-layout-panel-right:before { content: '\ebf1'; }
.codicon-layout-panel:before { content: '\ebf2'; }
.codicon-layout-sidebar-left:before { content: '\ebf3'; }
.codicon-layout-sidebar-right:before { content: '\ebf4'; }
.codicon-layout-statusbar:before { content: '\ebf5'; }
.codicon-layout-menubar:before { content: '\ebf6'; }
.codicon-layout-centered:before { content: '\ebf7'; }
.codicon-target:before { content: '\ebf8'; }
.codicon-indent:before { content: '\ebf9'; }
.codicon-record-small:before { content: '\ebfa'; }
.codicon-error-small:before { content: '\ebfb'; }
.codicon-terminal-decoration-error:before { content: '\ebfb'; }
.codicon-arrow-circle-down:before { content: '\ebfc'; }
.codicon-arrow-circle-left:before { content: '\ebfd'; }
.codicon-arrow-circle-right:before { content: '\ebfe'; }
.codicon-arrow-circle-up:before { content: '\ebff'; }
.codicon-layout-sidebar-right-off:before { content: '\ec00'; }
.codicon-layout-panel-off:before { content: '\ec01'; }
.codicon-layout-sidebar-left-off:before { content: '\ec02'; }
.codicon-blank:before { content: '\ec03'; }
.codicon-heart-filled:before { content: '\ec04'; }
.codicon-map:before { content: '\ec05'; }
.codicon-map-horizontal:before { content: '\ec05'; }
.codicon-fold-horizontal:before { content: '\ec05'; }
.codicon-map-filled:before { content: '\ec06'; }
.codicon-map-horizontal-filled:before { content: '\ec06'; }
.codicon-fold-horizontal-filled:before { content: '\ec06'; }
.codicon-circle-small:before { content: '\ec07'; }
.codicon-bell-slash:before { content: '\ec08'; }
.codicon-bell-slash-dot:before { content: '\ec09'; }
.codicon-comment-unresolved:before { content: '\ec0a'; }
.codicon-git-pull-request-go-to-changes:before { content: '\ec0b'; }
.codicon-git-pull-request-new-changes:before { content: '\ec0c'; }
.codicon-search-fuzzy:before { content: '\ec0d'; }
.codicon-comment-draft:before { content: '\ec0e'; }
.codicon-send:before { content: '\ec0f'; }
.codicon-sparkle:before { content: '\ec10'; }
.codicon-insert:before { content: '\ec11'; }
.codicon-mic:before { content: '\ec12'; }
.codicon-thumbsdown-filled:before { content: '\ec13'; }
.codicon-thumbsup-filled:before { content: '\ec14'; }
.codicon-coffee:before { content: '\ec15'; }
.codicon-snake:before { content: '\ec16'; }
.codicon-game:before { content: '\ec17'; }
.codicon-vr:before { content: '\ec18'; }
.codicon-chip:before { content: '\ec19'; }
.codicon-piano:before { content: '\ec1a'; }
.codicon-music:before { content: '\ec1b'; }
.codicon-mic-filled:before { content: '\ec1c'; }
.codicon-repo-fetch:before { content: '\ec1d'; }
.codicon-copilot:before { content: '\ec1e'; }
.codicon-lightbulb-sparkle:before { content: '\ec1f'; }
.codicon-robot:before { content: '\ec20'; }
.codicon-sparkle-filled:before { content: '\ec21'; }
.codicon-diff-single:before { content: '\ec22'; }
.codicon-diff-multiple:before { content: '\ec23'; }
.codicon-surround-with:before { content: '\ec24'; }
.codicon-share:before { content: '\ec25'; }
.codicon-git-stash:before { content: '\ec26'; }
.codicon-git-stash-apply:before { content: '\ec27'; }
.codicon-git-stash-pop:before { content: '\ec28'; }
.codicon-vscode:before { content: '\ec29'; }
.codicon-vscode-insiders:before { content: '\ec2a'; }
.codicon-code-oss:before { content: '\ec2b'; }
.codicon-run-coverage:before { content: '\ec2c'; }
.codicon-run-all-coverage:before { content: '\ec2d'; }
.codicon-coverage:before { content: '\ec2e'; }
.codicon-github-project:before { content: '\ec2f'; }
.codicon-map-vertical:before { content: '\ec30'; }
.codicon-fold-vertical:before { content: '\ec30'; }
.codicon-map-vertical-filled:before { content: '\ec31'; }
.codicon-fold-vertical-filled:before { content: '\ec31'; }
.codicon-go-to-search:before { content: '\ec32'; }
.codicon-percentage:before { content: '\ec33'; }
.codicon-sort-percentage:before { content: '\ec33'; }
.codicon-attach:before { content: '\ec34'; }
.codicon-git-fetch:before { content: '\f101'; }

BIN
tests/assets/codicon.ttf Normal file

Binary file not shown.

View File

@ -15,26 +15,373 @@
* limitations under the License.
*/
import type { Locator } from '@playwright/test';
import { test as it, expect } from './pageTest';
it('should snapshot the check box @smoke', async ({ page }) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox');
function unshift(snapshot: string): string {
const lines = snapshot.split('\n');
let whitespacePrefixLength = 100;
for (const line of lines) {
if (!line.trim())
continue;
const match = line.match(/^(\s*)/);
if (match && match[1].length < whitespacePrefixLength)
whitespacePrefixLength = match[1].length;
break;
}
return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n');
}
async function checkAndMatchSnapshot(locator: Locator, snapshot: string) {
expect.soft(await locator.ariaSnapshot()).toBe(unshift(snapshot));
await expect.soft(locator).toMatchAriaSnapshot(snapshot);
}
it('should snapshot', async ({ page }) => {
await page.setContent(`<h1>title</h1>`);
await checkAndMatchSnapshot(page.locator('body'), `
- heading "title"
`);
});
it('should snapshot nested element', async ({ page }) => {
it('should snapshot list', async ({ page }) => {
await page.setContent(`
<div>
<input id='checkbox' type='checkbox'></input>
</div>`);
expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox');
<h1>title</h1>
<h1>title 2</h1>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- heading "title"
- heading "title 2"
`);
});
it('should snapshot fragment', async ({ page }) => {
it('should snapshot list with accessible name', async ({ page }) => {
await page.setContent(`
<div>
<a href="about:blank">Link</a>
<a href="about:blank">Link</a>
</div>`);
expect(await page.locator('body').ariaSnapshot()).toBe(`- link "Link"\n- link "Link"`);
<ul aria-label="my list">
<li>one</li>
<li>two</li>
</ul>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- list "my list":
- listitem: one
- listitem: two
`);
});
it('should snapshot complex', async ({ page }) => {
await page.setContent(`
<ul>
<li>
<a href='about:blank'>link</a>
</li>
</ul>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- list:
- listitem:
- link "link"
`);
});
it('should allow text nodes', async ({ page }) => {
await page.setContent(`
<h1>Microsoft</h1>
<div>Open source projects and samples from Microsoft</div>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- heading "Microsoft"
- text: Open source projects and samples from Microsoft
`);
});
it('should snapshot details visibility', async ({ page }) => {
await page.setContent(`
<details>
<summary>Summary</summary>
<div>Details</div>
</details>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- group: Summary
`);
});
it('should snapshot integration', async ({ page }) => {
await page.setContent(`
<h1>Microsoft</h1>
<div>Open source projects and samples from Microsoft</div>
<ul>
<li>
<details>
<summary>
Verified
</summary>
<div>
<div>
<p>
We've verified that the organization <strong>microsoft</strong> controls the domain:
</p>
<ul>
<li class="mb-1">
<strong>opensource.microsoft.com</strong>
</li>
</ul>
<div>
<a href="about: blank">Learn more about verified organizations</a>
</div>
</div>
</div>
</details>
</li>
<li>
<a href="about:blank">
<summary title="Label: GitHub Sponsor">Sponsor</summary>
</a>
</li>
</ul>`);
await checkAndMatchSnapshot(page.locator('body'), `
- heading "Microsoft"
- text: Open source projects and samples from Microsoft
- list:
- listitem:
- group: Verified
- listitem:
- link "Sponsor"
`);
});
it('should support multiline text', async ({ page }) => {
await page.setContent(`
<p>
Line 1
Line 2
Line 3
</p>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- paragraph: Line 1 Line 2 Line 3
`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- paragraph: |
Line 1
Line 2
Line 3
`);
});
it('should concatenate span text', async ({ page }) => {
await page.setContent(`
<span>One</span> <span>Two</span> <span>Three</span>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- text: One Two Three
`);
});
it('should concatenate span text 2', async ({ page }) => {
await page.setContent(`
<span>One </span><span>Two </span><span>Three</span>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- text: One Two Three
`);
});
it('should concatenate div text with spaces', async ({ page }) => {
await page.setContent(`
<div>One</div><div>Two</div><div>Three</div>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- text: One Two Three
`);
});
it('should include pseudo in text', async ({ page }) => {
await page.setContent(`
<style>
span:before {
content: 'world';
}
div:after {
content: 'bye';
}
</style>
<a href="about:blank">
<span>hello</span>
<div>hello</div>
</a>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- link "worldhello hellobye"
`);
});
it('should not include hidden pseudo in text', async ({ page }) => {
await page.setContent(`
<style>
span:before {
content: 'world';
display: none;
}
div:after {
content: 'bye';
visibility: hidden;
}
</style>
<a href="about:blank">
<span>hello</span>
<div>hello</div>
</a>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- link "hello hello"
`);
});
it('should include new line for block pseudo', async ({ page }) => {
await page.setContent(`
<style>
span:before {
content: 'world';
display: block;
}
div:after {
content: 'bye';
display: block;
}
</style>
<a href="about:blank">
<span>hello</span>
<div>hello</div>
</a>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- link "world hello hello bye"
`);
});
it('should work with slots', async ({ page }) => {
// Text "foo" is assigned to the slot, should not be used twice.
await page.setContent(`
<button><div>foo</div></button>
<script>
(() => {
const container = document.querySelector('div');
const shadow = container.attachShadow({ mode: 'open' });
const slot = document.createElement('slot');
shadow.appendChild(slot);
})();
</script>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- button "foo"
`);
// Text "foo" is assigned to the slot, should be used instead of slot content.
await page.setContent(`
<div>foo</div>
<script>
(() => {
const container = document.querySelector('div');
const shadow = container.attachShadow({ mode: 'open' });
const button = document.createElement('button');
shadow.appendChild(button);
const slot = document.createElement('slot');
button.appendChild(slot);
const span = document.createElement('span');
span.textContent = 'pre';
slot.appendChild(span);
})();
</script>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- button "foo"
`);
// Nothing is assigned to the slot, should use slot content.
await page.setContent(`
<div></div>
<script>
(() => {
const container = document.querySelector('div');
const shadow = container.attachShadow({ mode: 'open' });
const button = document.createElement('button');
shadow.appendChild(button);
const slot = document.createElement('slot');
button.appendChild(slot);
const span = document.createElement('span');
span.textContent = 'pre';
slot.appendChild(span);
})();
</script>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- button "pre"
`);
});
it('should snapshot inner text', async ({ page }) => {
await page.setContent(`
<div role="listitem">
<div>
<div>
<span title="a.test.ts">a.test.ts</span>
</div>
<div>
<button title="Run"></button>
<button title="Show source"></button>
<button title="Watch"></button>
</div>
</div>
</div>
<div role="listitem">
<div>
<div>
<span title="snapshot">snapshot</span>
</div>
<div class="ui-mode-list-item-time">30ms</div>
<div>
<button title="Run"></button>
<button title="Show source"></button>
<button title="Watch"></button>
</div>
</div>
</div>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- listitem:
- text: a.test.ts
- button "Run"
- button "Show source"
- button "Watch"
- listitem:
- text: snapshot 30ms
- button "Run"
- button "Show source"
- button "Watch"
`);
});
it('should include pseudo codepoints', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<link href="codicon.css" rel="stylesheet" />
<p class='codicon codicon-check'>hello</p>
`);
await checkAndMatchSnapshot(page.locator('body'), `
- paragraph: \ueab2hello
`);
});

View File

@ -94,7 +94,7 @@ test('should allow text nodes', async ({ page }) => {
`);
});
test('details visibility', async ({ page, browserName }) => {
test('details visibility', async ({ page }) => {
await page.setContent(`
<details>
<summary>Summary</summary>
@ -107,7 +107,7 @@ test('details visibility', async ({ page, browserName }) => {
`);
});
test('integration test', async ({ page, browserName }) => {
test('integration test', async ({ page }) => {
await page.setContent(`
<h1>Microsoft</h1>
<div>Open source projects and samples from Microsoft</div>

View File

@ -5,16 +5,15 @@
"packages": {
"": {
"dependencies": {
"@playwright/test": "1.48.0-beta-1728384960000"
"@playwright/test": "1.49.0-alpha-2024-10-17"
}
},
"node_modules/@playwright/test": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-bqQorY7LKVldgwAsUbjULdwKEoUlZ8OOHRZmM/1XyGiGqJwzTGdr0x8Ss312BvKddAh+5pz8cbaPopw10Rp3Ng==",
"license": "Apache-2.0",
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-HLZY3sM6xt9Wi8K09zPwjJQtcUBZNBcNSIVoMZhtJM3+TikCKx4SiJ3P8vbSlk7Tm3s2oqlS+wA181IxhbTGBA==",
"dependencies": {
"playwright": "1.48.0-beta-1728384960000"
"playwright": "1.49.0-alpha-2024-10-17"
},
"bin": {
"playwright": "cli.js"
@ -28,7 +27,6 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@ -38,12 +36,11 @@
}
},
"node_modules/playwright": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-5pIZTwoktOGYJL+YpF2RNhGzVUY6rA/ceQAT0lEQSZaL55MKUzraD2FAoZoBnz84cIIks2ZSlXt8j5mJ5xXt8g==",
"license": "Apache-2.0",
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-IgcLunnpocVS/AEq2lcftVOu0DGQzFm1Qt25SCJsrVvKVe83ElKXZYskPz7yA0HeuOVxQyN69EDWI09ph7lfoQ==",
"dependencies": {
"playwright-core": "1.48.0-beta-1728384960000"
"playwright-core": "1.49.0-alpha-2024-10-17"
},
"bin": {
"playwright": "cli.js"
@ -56,10 +53,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-atIhpuvqvVEW5luPhwzhdcXsGdPvzOBLXAg3+MvOLY+6Q4JcTfXMTtTmltP+llUV+LAgj38foQz+6tKTzNMlWg==",
"license": "Apache-2.0",
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-XLTKmPBm2ZIOXBckXtiimSOIjQsYy8MqEP9CsHSgytsP0E+j/44v1BuwHOOMaG8sfjcuZLZ1QdFidnl07A9wSg==",
"bin": {
"playwright-core": "cli.js"
},
@ -70,11 +66,11 @@
},
"dependencies": {
"@playwright/test": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-bqQorY7LKVldgwAsUbjULdwKEoUlZ8OOHRZmM/1XyGiGqJwzTGdr0x8Ss312BvKddAh+5pz8cbaPopw10Rp3Ng==",
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-HLZY3sM6xt9Wi8K09zPwjJQtcUBZNBcNSIVoMZhtJM3+TikCKx4SiJ3P8vbSlk7Tm3s2oqlS+wA181IxhbTGBA==",
"requires": {
"playwright": "1.48.0-beta-1728384960000"
"playwright": "1.49.0-alpha-2024-10-17"
}
},
"fsevents": {
@ -84,18 +80,18 @@
"optional": true
},
"playwright": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-5pIZTwoktOGYJL+YpF2RNhGzVUY6rA/ceQAT0lEQSZaL55MKUzraD2FAoZoBnz84cIIks2ZSlXt8j5mJ5xXt8g==",
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-IgcLunnpocVS/AEq2lcftVOu0DGQzFm1Qt25SCJsrVvKVe83ElKXZYskPz7yA0HeuOVxQyN69EDWI09ph7lfoQ==",
"requires": {
"fsevents": "2.3.2",
"playwright-core": "1.48.0-beta-1728384960000"
"playwright-core": "1.49.0-alpha-2024-10-17"
}
},
"playwright-core": {
"version": "1.48.0-beta-1728384960000",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0-beta-1728384960000.tgz",
"integrity": "sha512-atIhpuvqvVEW5luPhwzhdcXsGdPvzOBLXAg3+MvOLY+6Q4JcTfXMTtTmltP+llUV+LAgj38foQz+6tKTzNMlWg=="
"version": "1.49.0-alpha-2024-10-17",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-17.tgz",
"integrity": "sha512-XLTKmPBm2ZIOXBckXtiimSOIjQsYy8MqEP9CsHSgytsP0E+j/44v1BuwHOOMaG8sfjcuZLZ1QdFidnl07A9wSg=="
}
}
}

View File

@ -1,6 +1,6 @@
{
"private": true,
"dependencies": {
"@playwright/test": "1.48.0-beta-1728384960000"
"@playwright/test": "1.49.0-alpha-2024-10-17"
}
}