1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Desktop: Fix errors found by automated accessibility testing (#11246)

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
Henry Heino 2024-11-09 04:50:06 -08:00 committed by GitHub
parent b248700e28
commit a616dc3cd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 153 additions and 23 deletions

View File

@ -511,10 +511,12 @@ packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/extendedExpect.js packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/setDarkMode.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
packages/app-desktop/playwright.config.js packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js

2
.gitignore vendored
View File

@ -487,10 +487,12 @@ packages/app-desktop/integration-tests/util/createStartupArgs.js
packages/app-desktop/integration-tests/util/extendedExpect.js packages/app-desktop/integration-tests/util/extendedExpect.js
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
packages/app-desktop/integration-tests/util/getImageSourceSize.js packages/app-desktop/integration-tests/util/getImageSourceSize.js
packages/app-desktop/integration-tests/util/setDarkMode.js
packages/app-desktop/integration-tests/util/setFilePickerResponse.js packages/app-desktop/integration-tests/util/setFilePickerResponse.js
packages/app-desktop/integration-tests/util/setMessageBoxResponse.js packages/app-desktop/integration-tests/util/setMessageBoxResponse.js
packages/app-desktop/integration-tests/util/test.js packages/app-desktop/integration-tests/util/test.js
packages/app-desktop/integration-tests/util/waitForNextOpenPath.js packages/app-desktop/integration-tests/util/waitForNextOpenPath.js
packages/app-desktop/integration-tests/wcag.spec.js
packages/app-desktop/playwright.config.js packages/app-desktop/playwright.config.js
packages/app-desktop/plugins/GotoAnything.js packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js

View File

@ -224,7 +224,8 @@ const Button = React.forwardRef((props: Props, ref: any) => {
function renderIcon() { function renderIcon() {
if (!props.iconName) return null; if (!props.iconName) return null;
return <StyledIcon return <StyledIcon
aria-label={props.iconLabel ?? ''} aria-label={props.iconLabel ?? undefined}
aria-hidden={!props.iconLabel}
animation={props.iconAnimation} animation={props.iconAnimation}
mr={iconOnly ? '0' : '6px'} mr={iconOnly ? '0' : '6px'}
color={props.color} color={props.color}

View File

@ -438,7 +438,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
} }
return ( return (
<div className="config-screen" style={{ display: 'flex', flexDirection: 'row', height: this.props.style.height }}> <div className="config-screen" role="main" style={{ display: 'flex', flexDirection: 'row', height: this.props.style.height }}>
<Sidebar <Sidebar
selection={this.state.selectedSectionName} selection={this.state.selectedSectionName}
onSelectionChange={this.sidebar_selectionChange} onSelectionChange={this.sidebar_selectionChange}

View File

@ -56,7 +56,7 @@ export const StyledDivider = styled.div`
border-bottom: 1px solid ${(props: StyleProps) => props.theme.dividerColor}; border-bottom: 1px solid ${(props: StyleProps) => props.theme.dividerColor};
background-color: ${(props: StyleProps) => props.theme.selectedColor2}; background-color: ${(props: StyleProps) => props.theme.selectedColor2};
font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize)}px; font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize)}px;
opacity: 0.5; opacity: 0.58;
`; `;
export const StyledListItemLabel = styled.span` export const StyledListItemLabel = styled.span`
@ -131,9 +131,9 @@ export default function Sidebar(props: Props) {
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
> >
<StyledListItemIcon <StyledListItemIcon
aria-label=''
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)} className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
role='img' role='img'
aria-hidden='true'
/> />
<StyledListItemLabel> <StyledListItemLabel>
{Setting.sectionNameToLabel(section.name)} {Setting.sectionNameToLabel(section.name)}

View File

@ -6,7 +6,7 @@ interface Props {
} }
const SettingDescription: React.FC<Props> = props => { const SettingDescription: React.FC<Props> = props => {
return props.text ? <div className='setting-description' id={props.id}>{props.text}</div> : null; return <div className={`setting-description ${!props.text ? '-empty' : ''}`} id={props.id}>{props.text}</div>;
}; };
export default SettingDescription; export default SettingDescription;

View File

@ -6,4 +6,9 @@
font-style: italic; font-style: italic;
max-width: 70em; max-width: 70em;
margin-top: 5px; margin-top: 5px;
&.-empty {
margin: 0;
padding: 0;
}
} }

View File

@ -17,7 +17,7 @@ export default function(props: Props) {
} else if (folderIcon.type === FolderIconType.DataUrl) { } else if (folderIcon.type === FolderIconType.DataUrl) {
return <img style={{ width, height, opacity }} src={folderIcon.dataUrl} />; return <img style={{ width, height, opacity }} src={folderIcon.dataUrl} />;
} else if (folderIcon.type === FolderIconType.FontAwesome) { } else if (folderIcon.type === FolderIconType.FontAwesome) {
return <i style={{ fontSize: 18, width, opacity }} className={folderIcon.name} role='img'></i>; return <i style={{ fontSize: 18, width, opacity }} className={folderIcon.name} role='img' aria-hidden={true}></i>;
} else { } else {
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`); throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
} }

View File

@ -211,7 +211,6 @@ class ItemList<ItemType> extends React.Component<Props<ItemType>, State> {
id={this.props.id} id={this.props.id}
role={this.props.role} role={this.props.role}
aria-label={this.props['aria-label']} aria-label={this.props['aria-label']}
aria-setsize={items.length}
onScroll={this.onScroll} onScroll={this.onScroll}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}

View File

@ -633,10 +633,12 @@ class MainScreenComponent extends React.Component<Props, State> {
}, },
editor: () => { editor: () => {
return <NoteEditor return <div className='note-editor-wrapper' role='main' aria-label={_('Note')}>
windowId={defaultWindowId} <NoteEditor
key={key} windowId={defaultWindowId}
/>; key={key}
/>
</div>;
}, },
}; };

View File

@ -391,6 +391,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
spellcheckEnabled: Setting.value('editor.spellcheckBeta'), spellcheckEnabled: Setting.value('editor.spellcheckBeta'),
keymap: keyboardMode, keymap: keyboardMode,
indentWithTabs: true, indentWithTabs: true,
editorLabel: _('Markdown editor'),
}; };
}, [ }, [
props.contentMarkupLanguage, props.disabled, props.keyboardMode, styles.globalTheme, props.contentMarkupLanguage, props.disabled, props.keyboardMode, styles.globalTheme,

View File

@ -116,6 +116,7 @@ export default function NoteTitleBar(props: Props) {
type="text" type="text"
ref={props.titleInputRef} ref={props.titleInputRef}
placeholder={props.isProvisional ? (props.noteIsTodo ? _('Creating new to-do...') : _('Creating new note...')) : ''} placeholder={props.isProvisional ? (props.noteIsTodo ? _('Creating new to-do...') : _('Creating new note...')) : ''}
aria-label={props.isProvisional ? undefined : _('Note title')}
style={styles.titleInput} style={styles.titleInput}
readOnly={props.disabled} readOnly={props.disabled}
onChange={props.onTitleChange} onChange={props.onTitleChange}

View File

@ -282,12 +282,13 @@ const NoteList = (props: Props) => {
onItemContextMenu({ itemId: activeNoteId }); onItemContextMenu({ itemId: activeNoteId });
}, [onItemContextMenu, activeNoteId]); }, [onItemContextMenu, activeNoteId]);
const hasNotes = !!props.notes.length;
return ( return (
<div <div
role='listbox' role={hasNotes ? 'listbox' : 'group'}
aria-label={_('Notes')} aria-label={hasNotes ? _('Notes') : null}
aria-activedescendant={getNoteElementIdFromJoplinId(activeNoteId)} aria-activedescendant={activeNoteId ? getNoteElementIdFromJoplinId(activeNoteId) : undefined}
aria-multiselectable={true} aria-multiselectable={hasNotes ? true : undefined}
tabIndex={0} tabIndex={0}
onFocus={onFocus} onFocus={onFocus}

View File

@ -178,7 +178,7 @@ export default function NoteListWrapper(props: Props) {
}; };
return ( return (
<StyledRoot> <StyledRoot role='navigation' aria-label={_('Note list')}>
<NoteListControls <NoteListControls
height={controlHeight} height={controlHeight}
width={noteListSize.width} width={noteListSize.width}

View File

@ -6,6 +6,7 @@ import { focus } from '@joplin/lib/utils/focusHandler';
import { ForwardedRef, forwardRef, RefObject, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { ForwardedRef, forwardRef, RefObject, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { WindowIdContext } from './NewWindowOrIFrame'; import { WindowIdContext } from './NewWindowOrIFrame';
import useDocument from './hooks/useDocument'; import useDocument from './hooks/useDocument';
import { _ } from '@joplin/lib/locale';
interface Props { interface Props {
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
@ -224,6 +225,7 @@ const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerCon
style={viewerStyle} style={viewerStyle}
allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)' allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)'
allowFullScreen={true} allowFullScreen={true}
aria-label={_('Note editor')}
src={`joplin-content://note-viewer/${__dirname}/note-viewer/index.html`} src={`joplin-content://note-viewer/${__dirname}/note-viewer/index.html`}
></iframe> ></iframe>
); );

View File

@ -74,7 +74,7 @@ const SidebarComponent = (props: Props) => {
); );
return ( return (
<StyledRoot className="sidebar"> <StyledRoot className="sidebar" role='navigation' aria-label={_('Sidebar')}>
<div style={{ flex: 1 }}><FolderAndTagList/></div> <div style={{ flex: 1 }}><FolderAndTagList/></div>
<div style={{ flex: 0, padding: theme.mainPadding }}> <div style={{ flex: 0, padding: theme.mainPadding }}>
{syncReportComp} {syncReportComp}

View File

@ -33,7 +33,6 @@ const useOnRenderListWrapper = ({ selectedIndex, onKeyDown }: Props) => {
<div <div
role='tree' role='tree'
className='sidebar-list-items-wrapper' className='sidebar-list-items-wrapper'
aria-setsize={listItems.length}
tabIndex={allowContainerFocus ? 0 : undefined} tabIndex={allowContainerFocus ? 0 : undefined}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
> >

View File

@ -60,7 +60,7 @@ const AllNotesItem: React.FC<Props> = props => {
itemCount={props.itemCount} itemCount={props.itemCount}
> >
<EmptyExpandLink/> <EmptyExpandLink/>
<StyledAllNotesIcon aria-label='' role='img' className='icon-notes'/> <StyledAllNotesIcon aria-hidden='true' role='img' className='icon-notes'/>
<StyledListItemAnchor <StyledListItemAnchor
className="list-item" className="list-item"
isSpecialItem={true} isSpecialItem={true}

View File

@ -59,7 +59,7 @@ const HeaderItem: React.FC<Props> = props => {
onDrop={props.onDrop} onDrop={props.onDrop}
> >
<StyledHeader onClick={onClick}> <StyledHeader onClick={onClick}>
<StyledHeaderIcon aria-label='' role='img' className={item.iconName}/> <StyledHeaderIcon aria-hidden='true' role='img' className={item.iconName}/>
<StyledHeaderLabel>{item.label}</StyledHeaderLabel> <StyledHeaderLabel>{item.label}</StyledHeaderLabel>
</StyledHeader> </StyledHeader>
</ListItemWrapper> </ListItemWrapper>

View File

@ -36,7 +36,7 @@ export default function ToolbarButton(props: Props) {
const iconName = getProp(props, 'iconName'); const iconName = getProp(props, 'iconName');
if (iconName) { if (iconName) {
const iconProps: React.HTMLProps<HTMLDivElement> = { const iconProps: React.HTMLProps<HTMLDivElement> = {
'aria-label': '', 'aria-hidden': true,
role: 'img', role: 'img',
className: `toolbar-icon ${title ? '-has-title' : ''} ${iconName}`, className: `toolbar-icon ${title ? '-has-title' : ''} ${iconName}`,
}; };

View File

@ -9,4 +9,5 @@
@use './editor-toolbar.scss'; @use './editor-toolbar.scss';
@use './user-webview-dialog-container.scss'; @use './user-webview-dialog-container.scss';
@use './dialog-anchor-node.scss'; @use './dialog-anchor-node.scss';
@use './note-editor-wrapper.scss';
@use './text-input.scss'; @use './text-input.scss';

View File

@ -0,0 +1,6 @@
.note-editor-wrapper {
flex-grow: 1;
height: 100%;
width: 100%;
}

View File

@ -4,9 +4,11 @@ import { ElectronApplication, Locator, Page } from '@playwright/test';
export default class Sidebar { export default class Sidebar {
public readonly container: Locator; public readonly container: Locator;
public readonly allNotes: Locator;
public constructor(page: Page, private mainScreen: MainScreen) { public constructor(page: Page, private mainScreen: MainScreen) {
this.container = page.locator('.rli-sideBar'); this.container = page.locator('.rli-sideBar');
this.allNotes = this.container.getByText('All notes');
} }
public async createNewFolder(title: string) { public async createNewFolder(title: string) {

View File

@ -0,0 +1,9 @@
import { ElectronApplication } from '@playwright/test';
const setDarkMode = (app: ElectronApplication, darkMode: boolean) => {
return app.evaluate(({ nativeTheme }, darkMode) => {
nativeTheme.themeSource = darkMode ? 'dark' : 'light';
}, darkMode);
};
export default setDarkMode;

View File

@ -4,6 +4,7 @@ import { _electron as electron, Page, ElectronApplication, test as base } from '
import uuid from '@joplin/lib/uuid'; import uuid from '@joplin/lib/uuid';
import createStartupArgs from './createStartupArgs'; import createStartupArgs from './createStartupArgs';
import firstNonDevToolsWindow from './firstNonDevToolsWindow'; import firstNonDevToolsWindow from './firstNonDevToolsWindow';
import setDarkMode from './setDarkMode';
type StartWithPluginsResult = { app: ElectronApplication; mainWindow: Page }; type StartWithPluginsResult = { app: ElectronApplication; mainWindow: Page };
@ -61,6 +62,7 @@ export const test = base.extend<JoplinFixtures>({
electronApp: async ({ profileDirectory }, use) => { electronApp: async ({ profileDirectory }, use) => {
const startupArgs = createStartupArgs(profileDirectory); const startupArgs = createStartupArgs(profileDirectory);
const electronApp = await electron.launch({ args: startupArgs }); const electronApp = await electron.launch({ args: startupArgs });
await setDarkMode(electronApp, false);
await use(electronApp); await use(electronApp);

View File

@ -0,0 +1,68 @@
import { test, expect } from './util/test';
import MainScreen from './models/MainScreen';
import SettingsScreen from './models/SettingsScreen';
import AxeBuilder from '@axe-core/playwright';
import { Page } from '@playwright/test';
const createScanner = (page: Page) => {
return new AxeBuilder({ page })
.disableRules(['page-has-heading-one'])
.setLegacyMode(true);
};
// Fade-in transitions can cause color contrast issues if still running
// during a scan.
// See https://github.com/dequelabs/axe-core-npm/issues/952
const waitForAnimationsToEnd = (page: Page) => {
return page.locator('body').evaluate(element => {
const animationPromises = element
.getAnimations({ subtree: true })
.map(animation => animation.finished);
return Promise.all(animationPromises);
});
};
const expectNoViolations = async (page: Page) => {
await waitForAnimationsToEnd(page);
const results = await createScanner(page).analyze();
expect(results.violations).toEqual([]);
};
test.describe('wcag', () => {
for (const tabName of ['General', 'Plugins']) {
test(`should not detect significant issues in the settings screen ${tabName} tab`, async ({ electronApp, mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.waitFor();
await mainScreen.openSettings(electronApp);
// Should be on the settings screen
const settingsScreen = new SettingsScreen(mainWindow);
await settingsScreen.waitFor();
const tabLocator = settingsScreen.getTabLocator(tabName);
await tabLocator.click();
await expect(tabLocator).toBeFocused();
await expectNoViolations(mainWindow);
});
}
test('should not detect significant issues in the main screen with an open note', async ({ mainWindow }) => {
const mainScreen = new MainScreen(mainWindow);
await mainScreen.waitFor();
await mainScreen.createNewNote('Test');
// For now, activate all notes to make it active. When inactive, it causes a contrast warning.
// This seems to be allowed under WCAG 2.2 SC 1.4.3 under the "Incidental" exception.
await mainScreen.sidebar.allNotes.click();
// Ensure that `:hover` styling is consistent between tests:
await mainScreen.noteEditor.noteTitleInput.hover();
await expectNoViolations(mainWindow);
});
});

View File

@ -124,6 +124,7 @@
"homepage": "https://github.com/laurent22/joplin#readme", "homepage": "https://github.com/laurent22/joplin#readme",
"devDependencies": { "devDependencies": {
"7zip-bin": "5.2.0", "7zip-bin": "5.2.0",
"@axe-core/playwright": "4.10.0",
"@electron/rebuild": "3.6.0", "@electron/rebuild": "3.6.0",
"@joplin/default-plugins": "~3.2", "@joplin/default-plugins": "~3.2",
"@joplin/tools": "~3.2", "@joplin/tools": "~3.2",

View File

@ -348,6 +348,8 @@ function NoteEditor(props: Props, ref: any) {
autocompleteMarkup: Setting.value('editor.autocompleteMarkup'), autocompleteMarkup: Setting.value('editor.autocompleteMarkup'),
indentWithTabs: true, indentWithTabs: true,
editorLabel: _('Markdown editor'),
}), [props.themeId, props.readOnly]); }), [props.themeId, props.readOnly]);
const injectedJavaScript = ` const injectedJavaScript = `

View File

@ -56,6 +56,7 @@ const configFromSettings = (settings: EditorSettings) => {
autocapitalize: 'sentence', autocapitalize: 'sentence',
autocorrect: settings.spellcheckEnabled ? 'true' : 'false', autocorrect: settings.spellcheckEnabled ? 'true' : 'false',
spellcheck: settings.spellcheckEnabled ? 'true' : 'false', spellcheck: settings.spellcheckEnabled ? 'true' : 'false',
'aria-label': settings.editorLabel,
}), }),
EditorState.readOnly.of(settings.readOnly), EditorState.readOnly.of(settings.readOnly),
indentUnit.of(settings.indentWithTabs ? '\t' : ' '), indentUnit.of(settings.indentWithTabs ? '\t' : ' '),

View File

@ -17,6 +17,7 @@ const createEditorSettings = (themeId: number) => {
themeData, themeData,
indentWithTabs: true, indentWithTabs: true,
editorLabel: 'Markdown editor',
}; };
return editorSettings; return editorSettings;

View File

@ -168,6 +168,8 @@ export interface EditorSettings {
readOnly: boolean; readOnly: boolean;
indentWithTabs: boolean; indentWithTabs: boolean;
editorLabel: string;
} }
export type LogMessageCallback = (message: string)=> void; export type LogMessageCallback = (message: string)=> void;

View File

@ -14,7 +14,7 @@ const theme: Theme = {
colorCorrect: 'green', // Opposite of colorError colorCorrect: 'green', // Opposite of colorError
colorWarn: 'rgb(228,86,0)', colorWarn: 'rgb(228,86,0)',
colorWarnUrl: '#155BDA', colorWarnUrl: '#155BDA',
colorFaded: '#7C8B9E', // For less important text colorFaded: '#627184', // For less important text
dividerColor: '#dddddd', dividerColor: '#dddddd',
selectedColor: '#e5e5e5', selectedColor: '#e5e5e5',
urlColor: '#155BDA', urlColor: '#155BDA',
@ -32,7 +32,7 @@ const theme: Theme = {
// It's dark text over gray background. // It's dark text over gray background.
backgroundColor3: '#F4F5F6', backgroundColor3: '#F4F5F6',
backgroundColorHover3: '#CBDAF1', backgroundColorHover3: '#CBDAF1',
color3: '#738598', color3: '#627284',
// Color scheme "4" is used for secondary-style buttons. It makes a white // Color scheme "4" is used for secondary-style buttons. It makes a white
// button with blue text. // button with blue text.

View File

@ -137,6 +137,7 @@ runtimes
onnx onnx
onnxruntime onnxruntime
treeitem treeitem
WCAG
qrcode qrcode
Rocketbook Rocketbook
datamatrix datamatrix

View File

@ -1434,6 +1434,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@axe-core/playwright@npm:4.10.0":
version: 4.10.0
resolution: "@axe-core/playwright@npm:4.10.0"
dependencies:
axe-core: ~4.10.0
peerDependencies:
playwright-core: ">= 1.0.0"
checksum: cba9b7f625c8ed510e661d8d014f7e924fb85175b918b818cb808eec848c3e8a2cbbe2960ccf8dd4b9f84a4a4ea33eeeace48de2560a4a5777dd0ee6281d0c81
languageName: node
linkType: hard
"@babel/code-frame@npm:7.10.4, @babel/code-frame@npm:~7.10.4": "@babel/code-frame@npm:7.10.4, @babel/code-frame@npm:~7.10.4":
version: 7.10.4 version: 7.10.4
resolution: "@babel/code-frame@npm:7.10.4" resolution: "@babel/code-frame@npm:7.10.4"
@ -8168,6 +8179,7 @@ __metadata:
resolution: "@joplin/app-desktop@workspace:packages/app-desktop" resolution: "@joplin/app-desktop@workspace:packages/app-desktop"
dependencies: dependencies:
7zip-bin: 5.2.0 7zip-bin: 5.2.0
"@axe-core/playwright": 4.10.0
"@electron/notarize": 2.3.2 "@electron/notarize": 2.3.2
"@electron/rebuild": 3.6.0 "@electron/rebuild": 3.6.0
"@electron/remote": 2.1.2 "@electron/remote": 2.1.2
@ -16062,6 +16074,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"axe-core@npm:~4.10.0":
version: 4.10.1
resolution: "axe-core@npm:4.10.1"
checksum: 1e71bc4b7cdad6e99dad9e4098a174932ed69052e7400e0fb57b585fff1764cc541580db375c643755250da7d68f811ba05fe0636c31d4238aa16e3f31587869
languageName: node
linkType: hard
"axios@npm:^0.25.0": "axios@npm:^0.25.0":
version: 0.25.0 version: 0.25.0
resolution: "axios@npm:0.25.0" resolution: "axios@npm:0.25.0"