diff --git a/.env b/.env index 5e4d7db..43732fe 100644 --- a/.env +++ b/.env @@ -5,4 +5,4 @@ THEME_NEUTRAL="#323130" THEME_NEUTRAL_LIGHT="#656462" THEME_NEUTRAL_DARK="#1F1E1D" SETTINGS_INFORMATION="

BAAT

Bookmarklet Accessibility Audit Tool v@VERSION@

BAAT is a tool for running automatic accessibility testing scripts directly in the browser and inspecting the results.

You can find the documentation on the BAAT GitHub Page.

" -AXE_MIN_URL="https://cdn.jsdelivr.net/npm/axe-core/axe.min.js" \ No newline at end of file +AXE_MIN_URL="https://baat.mscr.it/axe.min.js" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 43a4971..074dfe0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ build/ -intermediate/ \ No newline at end of file +intermediate/ +.idea/ \ No newline at end of file diff --git a/.idea/baat.iml b/.idea/baat.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/baat.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 8b33f45..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3ce3588..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 806e4a7..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 198e0d3..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/extras/iconset.svg b/extras/iconset.svg index 32e0cda..3d38ac1 100644 --- a/extras/iconset.svg +++ b/extras/iconset.svg @@ -8,4 +8,6 @@ + + \ No newline at end of file diff --git a/package.json b/package.json index 054e46e..add7900 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "baat", - "version": "1.2.1", + "version": "1.3-alpha", "description": "Axe frontend for running axe-core tests directly in the Browser", "main": "index.js", "scripts": { diff --git a/src/config.ts b/src/config.ts index 4bbac68..80f02bd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,9 @@ export const config = { defaultSettings: { hiddenTags: [] as string[], hiddenImpacts: [ 'moderate', 'minor' ], + hiddenResults: [] as string[], autorun: false, + differenceMode: false, developer: false, showAdditionalInformation: false, }, @@ -11,4 +13,21 @@ export const config = { width: 400, height: 650, } -}; \ No newline at end of file +}; + +export const settingNames = { + hiddenTags: 'hiddenTags', + hiddenImpacts: 'hiddenImpacts', + hiddenResults: 'hiddenResults', + autorun: 'autorun', + differenceMode: 'differenceMode', + developer: 'developer', + showAdditionalInformation: 'showAdditionalInformation', +} + +export const localStorageKeys = { + coreScript: 'baat_core_script', + settings: 'baat_settings', + history: 'baat_history', + view: 'baat_view', +} \ No newline at end of file diff --git a/src/core/BAAT.tsx b/src/core/BAAT.tsx index af7bdf6..68685aa 100644 --- a/src/core/BAAT.tsx +++ b/src/core/BAAT.tsx @@ -1,10 +1,19 @@ import { createScript } from '../util/dom' -import { config } from '../config' +import {config, localStorageKeys, settingNames} from '../config' import { axeExists } from '../util/axe' import * as axe from 'axe-core' -import { AxeRunCompleted, BAATEvent, BAATView, SettingsChanged, ViewChanged, Result, StatusChange } from '../types' -import { baact } from '../../baact/baact' +import { + AxeRunCompleted, + BAATEvent, + BAATView, + SettingsChanged, + ViewChanged, + Result, + StatusChange, + HistoryEntry +} from '../types' import { highlightContainer } from './highlight' +import {convertViolationToHistoryEntry} from "../util/history"; export class BAAT extends EventTarget { private static instance: BAAT; @@ -21,13 +30,13 @@ export class BAAT extends EventTarget { super(); const localStorage = window.localStorage - const possibleScript = localStorage.getItem('baat_core_script') + const possibleScript = localStorage.getItem(localStorageKeys.coreScript) try { - this.settings = JSON.parse(localStorage.getItem('baat_settings') ?? '{}') + this.settings = JSON.parse(localStorage.getItem(localStorageKeys.settings) ?? '{}') } catch (e) {} - this.addEventListener(BAATEvent.ChangeCore, () => { if (this.getSetting('autorun') && axeExists()) { window.setTimeout(() => { this.runAxe() }, 100) }}) + this.addEventListener(BAATEvent.ChangeCore, () => { if (this.getSetting(settingNames.autorun) && axeExists()) { window.setTimeout(() => { this.runAxe() }, 100) }}) if (possibleScript) { this.createScript(possibleScript) @@ -41,7 +50,7 @@ export class BAAT extends EventTarget { } - this._view = BAATView[(localStorage.getItem('baat_view') ?? BAATView.Settings.toString()) as keyof typeof BAATView] + this._view = BAATView[(localStorage.getItem(localStorageKeys.view) ?? BAATView.Settings.toString()) as keyof typeof BAATView] if (!axeExists()) { this._view = BAATView.Settings } @@ -52,7 +61,7 @@ export class BAAT extends EventTarget { if (script.includes('axe') && script.endsWith(';')) { new Promise((resolve) => { createScript(script, 'axeScript') - if (writeToStorage) localStorage.setItem('baat_core_script', script) + if (writeToStorage) localStorage.setItem(localStorageKeys.coreScript, script) this.dispatchEvent(new CustomEvent(BAATEvent.ChangeCore, { detail: { source } })) resolve() }) @@ -65,7 +74,7 @@ export class BAAT extends EventTarget { unloadAxe() { // @ts-ignore axe = null - localStorage.setItem('baat_core_script', "") + localStorage.setItem(localStorageKeys.coreScript, "") this.dispatchEvent(new CustomEvent(BAATEvent.ChangeCore, { detail: { source: '' } })) } @@ -97,11 +106,28 @@ export class BAAT extends EventTarget { })) new Promise((resolve) => { - localStorage.setItem('baat_settings', JSON.stringify(this.settings)) + localStorage.setItem(localStorageKeys.settings, JSON.stringify(this.settings)) resolve() }) } + addHistory(violations: Result[]) { + const history = localStorage.getItem(localStorageKeys.history) + const historyArray = history ? JSON.parse(history) : [] + const newEntry: HistoryEntry = convertViolationToHistoryEntry(violations); + historyArray.push(newEntry); + localStorage.setItem(localStorageKeys.history, JSON.stringify(historyArray)) + } + + getHistory(): HistoryEntry[] { + const history = localStorage.getItem(localStorageKeys.history) + return history ? JSON.parse(history) : [] + } + + clearHistory() { + localStorage.setItem(localStorageKeys.history, JSON.stringify([])) + } + dispatchStatusEvent(message: string) { this.dispatchEvent(new CustomEvent(BAATEvent.StatusChange, { detail: { message } })) } @@ -135,7 +161,7 @@ export class BAAT extends EventTarget { let violations = results.violations as Result[] if (results.violations.length) { - if (this.getSetting('developer')) + if (this.getSetting(settingNames.developer)) console.log('violations', violations) /*violations.forEach((violation) => { @@ -151,6 +177,8 @@ export class BAAT extends EventTarget { this.dispatchStatusEvent('') + this.addHistory(violations); + this.lastResults = violations this.fullReport = results this._hasRun = true; @@ -165,7 +193,7 @@ export class BAAT extends EventTarget { set view(value: BAATView) { this._view = value this.dispatchEvent(new CustomEvent(BAATEvent.ChangeView,{ detail: { view: value }})) - localStorage.setItem('baat_view', value.toString()) + localStorage.setItem(localStorageKeys.view, value.toString()) } get hasRun(): boolean { diff --git a/src/elements/Accordion/Accordion.tsx b/src/elements/Accordion/Accordion.tsx index 2b791b8..5f7fbee 100644 --- a/src/elements/Accordion/Accordion.tsx +++ b/src/elements/Accordion/Accordion.tsx @@ -5,7 +5,6 @@ import { baact, createRef } from '../../../baact/baact' import { css } from '../../util/taggedString' const border = `${theme.sizing.absolute.tiny} solid ${theme.palette.gray}` -const contentBorder = `${theme.sizing.absolute.normal} solid ${theme.palette.gray}` const styles = css` #container { @@ -38,6 +37,12 @@ const styles = css` .fixed #handle { cursor: revert; } + .nestedRoot #content { + padding: 0; + } + .nestedRoot.open #content { + border-bottom: none; + } #caret { margin: 0 ${theme.sizing.relative.tiny}; } @@ -47,7 +52,9 @@ const styles = css` #content { display: none; padding: ${theme.sizing.relative.tiny}; - border-left: ${contentBorder}; + border-left-color: var(--border-color, ${theme.palette.gray}); + border-left-width: ${theme.sizing.absolute.normal}; + border-left-style: solid; } .open #content { display: revert; @@ -59,13 +66,18 @@ const styles = css` interface IAccordionAccessor { folded: boolean fixed: boolean + nestedRoot?: boolean + borderColor?: string onChange?: (folded: boolean) => void } export class Accordion extends BaseHTMLElement implements IAccordionAccessor { public static tagName: string = 'baat-accordion' + public static slots = { heading: 'heading' } folded: boolean = true fixed: boolean = false + nestedRoot?: boolean = false + borderColor?: string private containerRef = createRef() private contentRef = createRef() onChange?: (folded: boolean) => void @@ -79,10 +91,16 @@ export class Accordion extends BaseHTMLElement implements IA case 'fixed': this.updateFixed() break + case 'nestedRoot': + this.updateNestedRoot() + break + case 'borderColor': + this.updateBorderColor() + break } } - static get observedAttributes(): (keyof IAccordionAccessor)[] { return [ 'folded', 'fixed' ] } + static get observedAttributes(): (keyof IAccordionAccessor)[] { return [ 'folded', 'fixed', 'nestedRoot', 'borderColor' ] } constructor() { super() @@ -102,6 +120,18 @@ export class Accordion extends BaseHTMLElement implements IA this.containerRef.value?.classList.toggle('fixed', this.fixed) } + updateNestedRoot() { + if (!this.shadowRoot) return; + + this.containerRef.value?.classList.toggle('nestedRoot', !!this.nestedRoot) + } + + updateBorderColor() { + if (!this.shadowRoot) return; + + this.containerRef.value?.style.setProperty('--border-color', this.borderColor ?? theme.palette.gray) + } + initialize() { const handleMouseUp = (e: Event) => { if ((window.getSelection()?.toString().length ?? 0) === 0 && !this.fixed) { @@ -125,6 +155,8 @@ export class Accordion extends BaseHTMLElement implements IA this.updateFixed() this.updateFolded() + this.updateNestedRoot() + this.updateBorderColor() this.initialized = true } diff --git a/src/elements/HiddenViolation/HiddenViolation.tsx b/src/elements/HiddenViolation/HiddenViolation.tsx new file mode 100644 index 0000000..c2b520e --- /dev/null +++ b/src/elements/HiddenViolation/HiddenViolation.tsx @@ -0,0 +1,118 @@ +import { NodeResultLink } from '../NodeResultLink/NodeResultLink' +import { BaseHTMLElement } from '../BaseHTMLElement' +import { css } from '../../util/taggedString' +import { theme } from '../../theme' +import { baact, createRef } from '../../../baact/baact' +import { NodeResult, Result } from '../../types' +import { Icon } from '..' +import {baatSymbol} from "../../core/BAAT"; +import {settingNames} from "../../config"; + +const borderBottom = `${theme.sizing.absolute.tiny} solid ${theme.palette.gray}`; + +const styles = css` + .container { + padding: ${theme.sizing.relative.tiny}; + border-bottom: ${borderBottom}; + display: flex; + gap: ${theme.sizing.relative.tiny}; + justify-content: space-between; + } + + h2 { + margin: 0; + font-size: ${theme.semanticSizing.font.normal}; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + font-weight: normal; + } + + button { + display: flex; + align-items: center; + font-family: sans-serif; + gap: ${theme.sizing.relative.tiny}; + background-color: ${theme.palette.white}; + border: none; + padding: ${theme.sizing.relative.tiny} ${theme.sizing.relative.smaller}; + cursor: pointer; + font-size: 1rem; + margin: -${theme.sizing.relative.tiny}; + } + + button:hover { + background-color: ${theme.palette.gray}; + } +`; + +interface IHiddenViolationAccessor { + result?: Result +} + +function createNodeLink(index: number, result: NodeResult, alternativeText?: string): HTMLLIElement { + return
  • + +
  • as unknown as HTMLLIElement; +} + +export class HiddenViolation extends BaseHTMLElement implements IHiddenViolationAccessor { + public static tagName: string = 'baat-hidden-violation' + result?: Result + folded: boolean = true + styles = styles + private titleRef = createRef() + + attributeChangedCallback(name: T, oldValue: IHiddenViolationAccessor[T], newValue: IHiddenViolationAccessor[T]) { + switch (name) { + case 'result': + this.updateResult() + break + } + } + + static get observedAttributes(): (keyof IHiddenViolationAccessor)[] { return [ 'result' ] } + + constructor() { + super() + this.attachShadow({ mode: 'open' }) + } + + updateResult() { + if (!this.shadowRoot || !this.isConnected) return + + if (!this.result) { + this.titleRef.value.innerText = ''; + } else { + this.titleRef.value.innerText = this.result.help; + } + } + + initialize() { + const handleShow = () => { + if (!this.result) return; + + window[baatSymbol].setSetting(settingNames.hiddenResults, + window[baatSymbol].getSetting(settingNames.hiddenResults).filter(hidden => hidden !== this.result.id) + ) + } + + this.shadowRoot?.appendChild( +
    +

    + +
    + ); + + this.updateResult() + } +} + +export const register = () => { + if (!customElements.get(HiddenViolation.tagName)) { // @ts-ignore + customElements.define(HiddenViolation.tagName, HiddenViolation) + } +} \ No newline at end of file diff --git a/src/elements/LibSelection/LibSelection.tsx b/src/elements/LibSelection/LibSelection.tsx index ce0ed59..f2b93c9 100644 --- a/src/elements/LibSelection/LibSelection.tsx +++ b/src/elements/LibSelection/LibSelection.tsx @@ -6,7 +6,8 @@ import { css } from '../../util/taggedString' import { baatSymbol } from '../../core/BAAT' import { baact, createRef } from '../../../baact/baact' import { theme } from '../../theme' -import { AxeRunCompleted, BAATEvent, ChangeCore } from '../../types' +import { BAATEvent, ChangeCore } from '../../types' +import {button} from "../../styles/button"; const styles = css` .visuallyHidden { ${visuallyHiddenStyles} } @@ -18,24 +19,8 @@ const styles = css` #fileButton { margin: 0.5em 0; } - - button { - background-color: ${theme.palette.primary}; - color: ${theme.palette.light}; - font-size: ${theme.semanticSizing.font.normal}; - border: none; - padding: ${theme.semanticSizing.button.padding}; - transition: background-color 0.2s ease-in-out; - cursor: pointer; - } - button:hover { - background-color: ${theme.palette.primaryDark}; - } - - button:active { - background-color: ${theme.palette.primaryLight}; - } + ${button} input { font-size: ${theme.semanticSizing.font.normal}; diff --git a/src/elements/NodeResultLink/NodeResultLink.tsx b/src/elements/NodeResultLink/NodeResultLink.tsx index cf5be07..392d09d 100644 --- a/src/elements/NodeResultLink/NodeResultLink.tsx +++ b/src/elements/NodeResultLink/NodeResultLink.tsx @@ -7,6 +7,7 @@ import { isHidden, ownText, removeAllChildren } from '../../util/dom' import { baatSymbol } from '../../core/BAAT' import { BAATEvent, HighlightElement, SettingsChanged } from '../../types' import { Icon } from '..' +import {settingNames} from "../../config"; const styles = css` :host, button { @@ -79,7 +80,7 @@ export class NodeResultLink extends BaseHTMLElement implement if (!this.shadowRoot || !this.isConnected) return let name = "" let hasLink = !isHidden(this.result?.element) - const devMode = window[baatSymbol].getSetting('developer') + const devMode = window[baatSymbol].getSetting(settingNames.developer) removeAllChildren(this.buttonRef.value) @@ -99,7 +100,7 @@ export class NodeResultLink extends BaseHTMLElement implement } this.buttonRef.value.appendChild(document.createTextNode(name)) - this.infoRef.value.innerText = window[baatSymbol].getSetting('showAdditionalInformation') + this.infoRef.value.innerText = window[baatSymbol].getSetting(settingNames.showAdditionalInformation) ? this.result?.failureSummary ?? '' : '' } @@ -111,14 +112,14 @@ export class NodeResultLink extends BaseHTMLElement implement if (this.result.element) window[baatSymbol].dispatchEvent(new CustomEvent(BAATEvent.HighlightElement,{ detail: { element: this.result.element }})) - if (window[baatSymbol].getSetting('developer')) + if (window[baatSymbol].getSetting(settingNames.developer)) console.log(this.result.element) this.result.element?.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' }) } window[baatSymbol].addEventListener(BAATEvent.ChangeSettings, ((event: CustomEvent) => { - if (event.detail.name === 'developer' || event.detail.name === 'showAdditionalInformation') this.update() + if (event.detail.name === settingNames.developer || event.detail.name === settingNames.showAdditionalInformation) this.update() }) as EventListener) this.shadowRoot?.appendChild(
    diff --git a/src/elements/Overlay/Overlay.tsx b/src/elements/Overlay/Overlay.tsx index d143bee..9087734 100644 --- a/src/elements/Overlay/Overlay.tsx +++ b/src/elements/Overlay/Overlay.tsx @@ -1,11 +1,13 @@ import { BaseHTMLElement } from '../BaseHTMLElement' import { css } from '../../util/taggedString' -import { baact } from '../../../baact/baact' import { baatSymbol } from '../../core/BAAT' import { BAATEvent, HighlightElement } from '../../types' import { theme } from '../../theme' import { getBoundingBox } from '../../util/dom' +const outline = `0px solid ${ theme.palette.primaryLight}77`; +const background = `${ theme.palette.primaryLight }77`; +const outlineBlink = `50px solid ${ theme.palette.primary}dd;` const styles = css` :host { z-index: 999999; @@ -21,12 +23,12 @@ const styles = css` border-radius: 2px; pointer-events: none; transition: all 0.5s ease-out; - outline: 0px solid ${ theme.palette.primaryLight}77; + outline: ${ outline }; } .blink { - background-color: ${ theme.palette.primaryLight }77; - outline: 50px solid ${ theme.palette.primary}dd; + background-color: ${ background }; + outline: ${ outlineBlink }; } `; diff --git a/src/elements/Results/Results.tsx b/src/elements/Results/Results.tsx index 7ab58f2..7ed92d1 100644 --- a/src/elements/Results/Results.tsx +++ b/src/elements/Results/Results.tsx @@ -1,9 +1,10 @@ import { Violation } from '../Violation/Violation' +import { HiddenViolation } from '../HiddenViolation/HiddenViolation' import { visuallyHiddenStyles } from '../../util/style' import { BaseHTMLElement } from '../BaseHTMLElement'; import { css } from '../../util/taggedString' import { baatSymbol } from '../../core/BAAT' -import { AxeRunCompleted, BAATEvent, Result, StatusChange } from '../../types' +import {AxeRunCompleted, BAATEvent, Result, SettingsChanged, StatusChange} from '../../types' import { baact, createRef } from '../../../baact/baact' import { theme } from '../../theme' import { and, notNullish } from '../../util/logic' @@ -12,10 +13,11 @@ import { zip } from '../../util/object'; import { removeAllChildren } from '../../util/dom' import { Icon } from '../Icon/Icon' import { download } from '../../util/file' -import { FilterSettings } from '../FilterSettings/FilterSettings' import * as axe from 'axe-core' -import { Accordion } from '../Accordion/Accordion' import { Checkbox } from '../Checkbox/Checkbox' +import { Accordion } from '../Accordion/Accordion'; +import {settingNames} from "../../config"; +import {convertViolationToHistoryEntry, historyEntryDiff} from "../../util/history"; const styles = css` #container { @@ -80,6 +82,11 @@ const styles = css` #status:empty { padding: 0; } + .listheading { + margin: 0; + font-size: ${theme.semanticSizing.font.large}; + margin-left: ${theme.sizing.relative.tiny}; + } `; interface IResultsAccessor { @@ -89,12 +96,13 @@ interface IResultsAccessor { export class Results extends BaseHTMLElement implements IResultsAccessor { public static tagName: string = 'baat-results' results: Result[] = [] - folded: boolean = true styles = styles private resultsContainerRef = createRef() private statisticsContainerRef = createRef() private statusContainerRef = createRef() private filterPlaceholderRef = createRef() + private hiddenCountRef = createRef() + private hiddenContainerRef = createRef() attributeChangedCallback(name: T, oldValue: IResultsAccessor[T], newValue: IResultsAccessor[T]) { switch (name) { @@ -131,7 +139,9 @@ export class Results extends BaseHTMLElement implements IResul window[baatSymbol].addEventListener(BAATEvent.RunCompleted, ((e: CustomEvent) => { this.setAttribute('results', e.detail.violations) }) as EventListener) - window[baatSymbol].addEventListener(BAATEvent.ChangeSettings, () => this.handleChangeSettings()) + window[baatSymbol].addEventListener(BAATEvent.ChangeSettings, ((e: CustomEvent) => + this.handleChangeSettings(e.detail.name) + ) as EventListener) window[baatSymbol].addEventListener(BAATEvent.StatusChange, ((e: CustomEvent) => { this.statusContainerRef.value.textContent = e.detail.message @@ -144,11 +154,50 @@ export class Results extends BaseHTMLElement implements IResul removeAllChildren(this.resultsContainerRef.value) removeAllChildren(this.statisticsContainerRef.value) + const differenceMode = window[baatSymbol].getSetting(settingNames.differenceMode); + + let newList: JSX.Element | Element = this.resultsContainerRef.value; + let unchangedList: JSX.Element | Element = this.resultsContainerRef.value; + let newEntries: string[] = []; + + if (differenceMode) { + const history = window[baatSymbol].getHistory(); + const historyDiff = historyEntryDiff(history[history.length - 2] ?? [], convertViolationToHistoryEntry(this.results)); + newEntries = historyDiff.newEntries; + + if (newEntries.length > 0) { + newList = +

    New

    +
    + + this.resultsContainerRef.value.appendChild(newList); + } + + if (historyDiff.unchangedEntries.length > 0) { + unchangedList = + +

    Unchanged

    +
    + + this.resultsContainerRef.value.appendChild(unchangedList); + } + } + + this.results + .forEach((result) => { + const list = newEntries.includes(result.id) ? newList : unchangedList; + list.appendChild( + + ) + }) + + let hiddenList =

    Hidden

    + this.resultsContainerRef.value.appendChild(hiddenList); this.results .forEach((result) => { - this.resultsContainerRef.value.appendChild( - + hiddenList.appendChild( + ) }) @@ -203,12 +252,12 @@ export class Results extends BaseHTMLElement implements IResul { ...Object.entries(counts).sort(byImpact).map(([impact, [violations, elements]]) => { - const checked = !window[baatSymbol].getSetting('hiddenImpacts').includes(impact) + const checked = !window[baatSymbol].getSetting(settingNames.hiddenImpacts).includes(impact) function handleChange(this: HTMLInputElement) { if (this.checked) { - window[baatSymbol].setSetting('hiddenImpacts', window[baatSymbol].getSetting('hiddenImpacts').filter(hidden => hidden !== impact)) + window[baatSymbol].setSetting(settingNames.hiddenImpacts, window[baatSymbol].getSetting(settingNames.hiddenImpacts).filter(hidden => hidden !== impact)) } else { - window[baatSymbol].setSetting('hiddenImpacts', [ ...window[baatSymbol].getSetting('hiddenImpacts'), impact ]) + window[baatSymbol].setSetting(settingNames.hiddenImpacts, [ ...window[baatSymbol].getSetting(settingNames.hiddenImpacts), impact ]) } } return @@ -252,12 +301,23 @@ export class Results extends BaseHTMLElement implements IResul this.handleChangeSettings() } - handleChangeSettings(): void { - const hiddenTags = window[baatSymbol].getSetting('hiddenTags') - const hiddenImpacts = window[baatSymbol].getSetting('hiddenImpacts') + handleChangeSettings(reason?: string): void { + const hiddenTags = window[baatSymbol].getSetting(settingNames.hiddenTags) + const hiddenImpacts = window[baatSymbol].getSetting(settingNames.hiddenImpacts) + const hiddenResults = window[baatSymbol].getSetting(settingNames.hiddenResults) + + if (reason === settingNames.differenceMode) { + this.updateResults(); + } + + let hiddenCount = 0; - this.shadowRoot?.querySelectorAll(Violation.tagName) + this.shadowRoot?.querySelectorAll(`${Violation.tagName}, ${HiddenViolation.tagName}`) .forEach((violation) => { + const isHidden = hiddenResults.includes(violation.getAttribute('data-id') ?? ""); + const isViolation = violation.getAttribute('data-type') === 'violation'; + hiddenCount += isHidden && !isViolation ? 1 : 0; + violation.classList.toggle('visuallyHidden', ( !violation .getAttribute('data-tags') @@ -266,11 +326,17 @@ export class Results extends BaseHTMLElement implements IResul .reduce((acc, curr) => acc || curr, false) ?? false ) || ( hiddenImpacts.includes(violation.getAttribute('data-impact') ?? "") + ) || ( + isViolation ? isHidden : !isHidden )) }) const numFiltered = this.shadowRoot?.querySelectorAll(`${Violation.tagName}.visuallyHidden`).length; + + this.hiddenCountRef.value.innerText = `(${hiddenCount})`; + this.hiddenContainerRef.value?.classList.toggle('visuallyHidden', hiddenCount === 0) + this.filterPlaceholderRef?.value?.classList.toggle('visuallyHidden', !(numFiltered !== 0 && numFiltered === window[baatSymbol].lastResults.length)) } } diff --git a/src/elements/Settings/Settings.tsx b/src/elements/Settings/Settings.tsx index 51e20e8..03286c4 100644 --- a/src/elements/Settings/Settings.tsx +++ b/src/elements/Settings/Settings.tsx @@ -8,6 +8,8 @@ import { BAATEvent } from '../../types' import { theme } from '../../theme' import { axeExists } from '../../util/axe' import { Checkbox } from '../Checkbox/Checkbox' +import {button} from "../../styles/button"; +import {settingNames} from "../../config"; const styles = css` #container { @@ -23,12 +25,18 @@ const styles = css` margin: 0; font-size: ${theme.semanticSizing.font.large}; } + ${button} .settingsContainer { padding-top: ${theme.sizing.relative.tiny}; } [role=doc-subtitle] { font-style: italic; } + .actions { + display: flex; + flex-direction: row; + gap: ${theme.sizing.relative.tiny}; + } `; interface ISettingsAccessor { @@ -55,19 +63,19 @@ export class Settings extends BaseHTMLElement implements ISet this.shadowRoot?.appendChild(
    - Testscript + Testscript
    ('autorun')} - onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting('autorun', this.checked); if(axeExists()) { window[baatSymbol].runAxe() } }} + checked={window[baatSymbol].getSetting(settingNames.autorun)} + onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting(settingNames.autorun, this.checked); if(axeExists()) { window[baatSymbol].runAxe() } }} label='auto run when loaded' />
    {/* - Issue Tag filters + Issue Tag filters implements ISet /> */} - BAAT Settings + BAAT Settings
    ('showAdditionalInformation')} - onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting('showAdditionalInformation', this.checked) }} + checked={window[baatSymbol].getSetting(settingNames.showAdditionalInformation)} + onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting(settingNames.showAdditionalInformation, this.checked) }} label='Show a short summary for each test result' /> + (settingNames.differenceMode)} + onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting(settingNames.differenceMode, this.checked) }} + label='history difference mode' + /> ('developer')} - onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting('developer', this.checked) }} + checked={window[baatSymbol].getSetting(settingNames.developer)} + onChange={function (this: HTMLInputElement) { window[baatSymbol].setSetting(settingNames.developer, this.checked) }} label='developer mode' /> +
    + + +
    diff --git a/src/elements/Violation/Violation.tsx b/src/elements/Violation/Violation.tsx index aada454..6ae72c7 100644 --- a/src/elements/Violation/Violation.tsx +++ b/src/elements/Violation/Violation.tsx @@ -9,6 +9,8 @@ import { Accordion } from '../Accordion/Accordion' import { NodeResult, Result } from '../../types' import { hideHighlight, showHighlight } from '../../core/highlight' import { Icon } from '..' +import {baatSymbol} from "../../core/BAAT"; +import {settingNames} from "../../config"; const impactColors = { 'critical': 'impactCritical', @@ -31,14 +33,6 @@ const styles = css` font-size: ${theme.semanticSizing.font.large}; margin-right: 0.25em; } - #indicator { - position: absolute; - top: 0; - right: 0; - height: ${theme.sizing.relative.larger}; - width: ${theme.sizing.relative.larger}; - box-sizing: border-box; - } .shrink { width: calc(100% - 3em) } @@ -56,9 +50,12 @@ const styles = css` display: flex; align-items: center; justify-content: center; + top: 0; + right: 0; height: ${theme.sizing.relative.larger}; width: ${theme.sizing.relative.larger}; font-size: ${theme.sizing.relative.huge}; + box-sizing: border-box; } .impactCritical { background-color: ${theme.palette.critical}; @@ -74,8 +71,8 @@ const styles = css` } .impactNone { background-color: gray; - } - + } + a { color: ${ theme.semanticColors.font.link }; } @@ -85,6 +82,22 @@ const styles = css` a:disabled { color: ${theme.semanticColors.font.dark}; } + + button { + display: flex; + align-items: center; + font-family: sans-serif; + gap: ${theme.sizing.relative.tiny}; + background-color: ${theme.palette.white}; + border: none; + padding: ${theme.sizing.relative.tiny} ${theme.sizing.relative.smaller}; + cursor: pointer; + font-size: 1rem; + } + + button:hover { + background-color: ${theme.palette.gray}; + } `; interface IViolationAccessor { @@ -177,9 +190,15 @@ export class Violation extends BaseHTMLElement implements IV this.result?.nodes.forEach(folded ? hideHighlight : showHighlight) } + const handleHide = () => { + if (!this.result) return; + + window[baatSymbol].setSetting(settingNames.hiddenResults, [ ...window[baatSymbol].getSetting(settingNames.hiddenResults), this.result.id ]) + } + this.shadowRoot?.appendChild( -
    +

    @@ -191,6 +210,10 @@ export class Violation extends BaseHTMLElement implements IV
    +
      ); diff --git a/src/elements/index.ts b/src/elements/index.ts index 101bc25..58beacf 100644 --- a/src/elements/index.ts +++ b/src/elements/index.ts @@ -1,5 +1,6 @@ import { NodeResultLink, register as registerNodeLink } from './NodeResultLink/NodeResultLink' import { Violation, register as registerViolation } from './Violation/Violation' +import { HiddenViolation, register as registerHiddenViolation } from "./HiddenViolation/HiddenViolation"; import { Checkbox, register as registerCheckbox } from './Checkbox/Checkbox' import { Results, register as registerResults } from './Results/Results' import { MiniResults, register as registerMiniResults } from './Results/MiniResults' @@ -13,11 +14,12 @@ import { FilterSettings, register as registerFilterSettings } from './FilterSett import { Icon, register as registerIcon } from './Icon/Icon' import { Overlay, register as registerOverlay } from './Overlay/Overlay' -export { NodeResultLink, Violation, Checkbox, Results, MiniResults, Window, LibSelection, Switch, SwitchView, Settings, Accordion, FilterSettings, Icon, Overlay } +export { NodeResultLink, Violation, HiddenViolation, Checkbox, Results, MiniResults, Window, LibSelection, Switch, SwitchView, Settings, Accordion, FilterSettings, Icon, Overlay } export const register = () => { registerNodeLink() registerViolation() + registerHiddenViolation() registerCheckbox() registerResults() registerMiniResults() diff --git a/src/index.tsx b/src/index.tsx index 42d5e21..98715bd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ 'use strict' import { register, Results, Settings, Switch, SwitchView, Window, Icon, MiniResults, Overlay } from './elements' -import { config } from './config' +import {config, settingNames} from './config' import { baatSymbol } from './core/BAAT' import { baact, createRef } from '../baact/baact' import { BAATEvent, BAATView } from './types' @@ -30,7 +30,7 @@ const handlePlayClick = () => { window[baatSymbol].addEventListener(BAATEvent.ChangeCore, () => { runRef.value.disabled = !axeExists() - if (window[baatSymbol].getSetting('autorun') && axeExists()) { + if (window[baatSymbol].getSetting(settingNames.autorun) && axeExists()) { settingsRef.value.setAttribute('aria-pressed', 'false') window[baatSymbol].view = BAATView.Main } diff --git a/src/styles/button.ts b/src/styles/button.ts new file mode 100644 index 0000000..e7fbf1e --- /dev/null +++ b/src/styles/button.ts @@ -0,0 +1,22 @@ +import {theme} from "../theme"; +import {css} from "../util/taggedString"; + +export const button = css` + button { + background-color: ${theme.palette.primary}; + color: ${theme.palette.light}; + font-size: ${theme.semanticSizing.font.normal}; + border: none; + padding: ${theme.semanticSizing.button.padding}; + transition: background-color 0.2s ease-in-out; + cursor: pointer; + } + + button:hover { + background-color: ${theme.palette.primaryDark}; + } + + button:active { + background-color: ${theme.palette.primaryLight}; + } +`; \ No newline at end of file diff --git a/src/theme.ts b/src/theme.ts index b74367c..996a4c4 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -22,6 +22,8 @@ const palette = { seriousTransparent: 'rgba(255,208,176,0.25)', moderateTransparent: 'rgba(255,224,178,0.25)', minorTransparent: 'rgba(255,236,179,0.25)', + green: '#77ad5a', + blue: '#5572bd', } const sizing = { diff --git a/src/types.ts b/src/types.ts index e7dff39..18c5997 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,4 +47,14 @@ export interface NodeResult extends axe.NodeResult { export interface Result extends axe.Result { nodes: NodeResult[] -} \ No newline at end of file +} + +export type HistoryEntry = { + id: string + nodes: { + html: string + all: { id: string }[] + any: { id: string }[] + target: string[] + }[] +}[] \ No newline at end of file diff --git a/src/util/history.ts b/src/util/history.ts new file mode 100644 index 0000000..1ff1fa1 --- /dev/null +++ b/src/util/history.ts @@ -0,0 +1,50 @@ +import {HistoryEntry, Result} from "../types"; + +export const convertViolationToHistoryEntry = (violations: Result[]): HistoryEntry => violations.map((violation) => ({ + id: violation.id, + nodes: violation.nodes.map((nodeResult) => ({ + target: nodeResult.target, + html: nodeResult.html, + all: nodeResult.all.map((allResult) => ({ + id: allResult.id, + + })), + any: nodeResult.any.map((anyResult) => ({ + id: anyResult.id, + })) + })) +})); + +type HistoryDiff = { + newEntries: string[] + removedEntries: string[] + unchangedEntries: string[] +} + +export const historyEntryDiff = (oldHistory: HistoryEntry, newHistory: HistoryEntry): HistoryDiff => { + const newEntries: string[] = []; + const removedEntries: string[] = []; + const unchangedEntries: string[] = []; + + oldHistory.forEach((historyEntry) => { + if (!newHistory.find((entry) => entry.id === historyEntry.id)) { + removedEntries.push(historyEntry.id); + } + }); + + newHistory.forEach((historyEntry) => { + if (!oldHistory.find((entry) => entry.id === historyEntry.id)) { + newEntries.push(historyEntry.id); + } + }); + + if (unchangedEntries.length === 0) { + unchangedEntries.push(...oldHistory.filter((entry) => newHistory.find((newEntry) => newEntry.id === entry.id)).map((entry) => entry.id)); + } + + return { + newEntries, + removedEntries, + unchangedEntries, + } +} \ No newline at end of file