From 7c5f99143a1d97e294d21e14917f4963013fc6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Ham?= Date: Wed, 19 Jul 2023 11:08:15 +0100 Subject: [PATCH] feat(d.ts): improve axe.d.ts types (#4081) * fix(d.ts): target selector type for shadow dom Before shadow dom support string[] was the correct type for the returned selectors. From code we can see that SerialDqElement.selector is used to populate this, so the type should also be the same. * feat(d.ts): add missing properties to the RelatedNode interface RelatedNode should have the same selectors and the same element reference as NodeResult. These are populated in process-aggregate.js. * fix(d.ts): incomplete message should be optional I think this was unintentionally changed in #3966. From check definitions we can see that many of them don't specify the incomplete message, because they don't have incomplete as a possible result. * feat(d.ts): add stronger typing to Check evaluate and after functions More precise types make it easier to write these function in typescript. With this change, complier knows about this.data, this.async, etc. * feat(d.ts): allow rule.matches to be a function Like evaluate and after, matches can also be a string or a function, but this is not currently represented in types. * fix(d.ts): tags and actIds should be on Rule and not RuleMetadata The rule metadata object expected in this.configure and the one returned by getRules are not the same. From the code we can see that tags and actIds are read from the rule itself and not from the metadata object. * feat(d.ts): add types for more utils and commons functions --- axe.d.ts | 78 +++++++++++++++++++++++++++--- typings/axe-core/axe-core-tests.ts | 38 +++++++++++++-- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/axe.d.ts b/axe.d.ts index c174b24c0f..3b232dfb96 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -164,9 +164,9 @@ declare namespace axe { interface NodeResult { html: string; impact?: ImpactValue; - target: string[]; + target: UnlabelledFrameSelector; xpath?: string[]; - ancestry?: string[]; + ancestry?: UnlabelledFrameSelector; any: CheckResult[]; all: CheckResult[]; none: CheckResult[]; @@ -181,8 +181,11 @@ declare namespace axe { relatedNodes?: RelatedNode[]; } interface RelatedNode { - target: string[]; html: string; + target: UnlabelledFrameSelector; + xpath?: string[]; + ancestry?: UnlabelledFrameSelector; + element?: HTMLElement; } interface RuleLocale { [key: string]: { @@ -193,7 +196,7 @@ declare namespace axe { interface CheckMessages { pass: string | { [key: string]: string }; fail: string | { [key: string]: string }; - incomplete: string | { [key: string]: string }; + incomplete?: string | { [key: string]: string }; } interface CheckLocale { [key: string]: CheckMessages; @@ -257,10 +260,31 @@ declare namespace axe { brand?: string; application?: string; } + interface CheckHelper { + async: () => (result: boolean | undefined | Error) => void; + data: (data: unknown) => void; + relatedNodes: (nodes: Element[]) => void; + } + interface AfterResult { + id: string; + data?: unknown; + relatedNodes: SerialDqElement[]; + result: boolean | undefined; + node: SerialDqElement; + } interface Check { id: string; - evaluate?: Function | string; - after?: Function | string; + evaluate?: + | string + | (( + this: CheckHelper, + node: Element, + options: unknown, + virtualNode: VirtualNode + ) => boolean | undefined | void); + after?: + | string + | ((results: AfterResult[], options: unknown) => AfterResult[]); options?: any; matches?: string; enabled?: boolean; @@ -280,9 +304,10 @@ declare namespace axe { all?: string[]; none?: string[]; tags?: string[]; - matches?: string; + matches?: string | ((node: Element, virtualNode: VirtualNode) => boolean); reviewOnFail?: boolean; - metadata?: Omit; + actIds?: string[]; + metadata?: Omit; } interface AxePlugin { id: string; @@ -367,6 +392,42 @@ declare namespace axe { shadowSelect: (selector: CrossTreeSelector) => Element | null; shadowSelectAll: (selector: CrossTreeSelector) => Element[]; getStandards(): Required; + DqElement: new ( + elm: Element, + options?: { absolutePaths?: boolean } + ) => SerialDqElement; + uuid: ( + options?: { random?: Uint8Array | Array }, + buf?: Uint8Array | Array, + offset?: number + ) => string | Uint8Array | Array; + } + + interface Aria { + getRoleType: (role: string | Element | VirtualNode | null) => string | null; + } + + interface Dom { + isFocusable: (node: Element | VirtualNode) => boolean; + isNativelyFocusable: (node: Element | VirtualNode) => boolean; + } + + type AccessibleTextOptions = { + inControlContext?: boolean; + inLabelledByContext?: boolean; + }; + + interface Text { + accessibleText: ( + element: Element, + options?: AccessibleTextOptions + ) => string; + } + + interface Commons { + aria: Aria; + dom: Dom; + text: Text; } interface EnvironmentData { @@ -380,6 +441,7 @@ declare namespace axe { let version: string; let plugins: any; let utils: Utils; + let commons: Commons; /** * Source string to use as an injected script in Selenium diff --git a/typings/axe-core/axe-core-tests.ts b/typings/axe-core/axe-core-tests.ts index 309dc9e75a..d968b5c30c 100644 --- a/typings/axe-core/axe-core-tests.ts +++ b/typings/axe-core/axe-core-tests.ts @@ -221,9 +221,15 @@ var spec: axe.Spec = { checks: [ { id: 'custom-check', - evaluate: function () { + evaluate: function (node) { + this.relatedNodes([node]); + this.data('some data'); return true; }, + after: function (results) { + const id = results[0].id; + return results; + }, metadata: { impact: 'minor', messages: { @@ -235,6 +241,13 @@ var spec: axe.Spec = { } } } + }, + { + id: 'async-check', + evaluate: function (node) { + const done = this.async(); + done(true); + } } ], standards: { @@ -264,11 +277,15 @@ var spec: axe.Spec = { { id: 'custom-rule', any: ['custom-check'], + matches: function (node) { + return node.tagName === 'BODY'; + }, + tags: ['a'], + actIds: ['b'], metadata: { description: 'custom rule', help: 'different help', - helpUrl: 'https://example.com', - tags: ['custom'] + helpUrl: 'https://example.com' } } ] @@ -318,6 +335,10 @@ axe.configure({ incomplete: { foo: 'bar' } + }, + bar: { + pass: 'pass', + fail: 'fail' } } } @@ -360,3 +381,14 @@ var pluginSrc: axe.AxePlugin = { }; axe.registerPlugin(pluginSrc); axe.cleanup(); + +// Utils +const dqElement = new axe.utils.DqElement(document.body); +const element = axe.utils.shadowSelect(dqElement.selector[0]); +const uuid = axe.utils.uuid() as string; + +// Commons +axe.commons.aria.getRoleType('img'); +axe.commons.dom.isFocusable(document.body); +axe.commons.dom.isNativelyFocusable(document.body); +axe.commons.text.accessibleText(document.body);