Bug 1552066 - add ALL filter to accessibility checks toolbar. r=mtigley
authorYura Zenevich <yura.zenevich@gmail.com>
Thu, 06 Jun 2019 13:53:29 +0000
changeset 477666 4d37efc903e5352619af9c0cda2eb0c554613681
parent 477665 7dce7a586c91a2a26076583364cb9c3ff3b945c5
child 477667 e54cf94c44709cef253430a05447031e300f7856
push id36121
push userdvarga@mozilla.com
push dateFri, 07 Jun 2019 09:47:19 +0000
treeherdermozilla-central@9183f80b6bf0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmtigley
bugs1552066
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1552066 - add ALL filter to accessibility checks toolbar. r=mtigley Differential Revision: https://phabricator.services.mozilla.com/D33184
devtools/client/accessibility/actions/audit.js
devtools/client/accessibility/components/AccessibilityTreeFilter.js
devtools/client/accessibility/constants.js
devtools/client/accessibility/reducers/audit.js
devtools/client/accessibility/test/browser/browser.ini
devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js
devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js
devtools/client/accessibility/test/browser/head.js
devtools/client/accessibility/test/jest/components/__snapshots__/accessibility-tree-filter.test.js.snap
devtools/client/accessibility/test/jest/components/accessibility-tree-filter.test.js
devtools/client/locales/en-US/accessibility.properties
--- a/devtools/client/accessibility/actions/audit.js
+++ b/devtools/client/accessibility/actions/audit.js
@@ -1,28 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { AUDIT, AUDIT_PROGRESS, AUDITING, FILTER_TOGGLE } = require("../constants");
+const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
+const { AUDIT, AUDIT_PROGRESS, AUDITING, FILTER_TOGGLE, FILTERS } = require("../constants");
 
 exports.filterToggle = filter =>
   dispatch => dispatch({ filter, type: FILTER_TOGGLE });
 
 exports.auditing = filter =>
   dispatch => {
-    const auditing = [filter];
+    const auditing = filter === FILTERS.ALL ? Object.values(FILTERS) : [filter];
     return dispatch({ auditing, type: AUDITING });
   };
 
 exports.audit = (walker, filter) =>
   dispatch => new Promise(resolve => {
-    const types = [filter];
+    const types = filter === FILTERS.ALL ? Object.values(AUDIT_TYPE) : [filter];
     const auditEventHandler = ({ type, ancestries, progress }) => {
       switch (type) {
         case "error":
           walker.off("audit-event", auditEventHandler);
           dispatch({ type: AUDIT, error: true });
           resolve();
           break;
         case "completed":
--- a/devtools/client/accessibility/components/AccessibilityTreeFilter.js
+++ b/devtools/client/accessibility/components/AccessibilityTreeFilter.js
@@ -14,16 +14,17 @@ const ToggleButton = createFactory(requi
 
 const actions = require("../actions/audit");
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { FILTERS } = require("../constants");
 
 const TELEMETRY_AUDIT_ACTIVATED = "devtools.accessibility.audit_activated";
 const FILTER_LABELS = {
+  [FILTERS.ALL]: "accessibility.filter.all",
   [FILTERS.CONTRAST]: "accessibility.badge.contrast",
 };
 
 class AccessibilityTreeFilter extends Component {
   static get propTypes() {
     return {
       auditing: PropTypes.array.isRequired,
       filters: PropTypes.object.isRequired,
--- a/devtools/client/accessibility/constants.js
+++ b/devtools/client/accessibility/constants.js
@@ -33,16 +33,17 @@ exports.UPDATE_CAN_BE_DISABLED = "UPDATE
 exports.UPDATE_CAN_BE_ENABLED = "UPDATE_CAN_BE_ENABLED";
 exports.FILTER_TOGGLE = "FILTER_TOGGLE";
 exports.AUDIT = "AUDIT";
 exports.AUDITING = "AUDITING";
 exports.AUDIT_PROGRESS = "AUDIT_PROGRESS";
 
 // List of filters for accessibility checks.
 exports.FILTERS = {
+  ALL: "ALL",
   [AUDIT_TYPE.CONTRAST]: "CONTRAST",
 };
 
 // Ordered accessible properties to be displayed by the accessible component.
 exports.ORDERED_PROPS = [
   "name",
   "role",
   "actions",
--- a/devtools/client/accessibility/reducers/audit.js
+++ b/devtools/client/accessibility/reducers/audit.js
@@ -1,46 +1,71 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
+const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
 const {
   AUDIT,
   AUDITING,
   AUDIT_PROGRESS,
   FILTER_TOGGLE,
   FILTERS,
   RESET,
   SELECT,
 } = require("../constants");
 
 /**
  * Initial state definition
  */
 function getInitialState() {
   return {
     filters: {
+      [FILTERS.ALL]: false,
       [FILTERS.CONTRAST]: false,
     },
     auditing: [],
     progress: null,
   };
 }
 
+/**
+ * State with all filters active.
+ */
+function allActiveFilters() {
+  return {
+    [FILTERS.ALL]: true,
+    [FILTERS.CONTRAST]: true,
+  };
+}
+
 function audit(state = getInitialState(), action) {
   switch (action.type) {
     case FILTER_TOGGLE:
       const { filter } = action;
       let { filters } = state;
-      const active = !filters[filter];
-      filters = {
-        ...filters,
-        [filter]: active,
-      };
+      const isToggledToActive = !filters[filter];
+
+      if (filter === FILTERS.ALL) {
+        filters = isToggledToActive ? allActiveFilters() : getInitialState().filters;
+      } else {
+        filters = {
+          ...filters,
+          [filter]: isToggledToActive,
+        };
+
+        if (isToggledToActive && !filters[FILTERS.ALL] &&
+            Object.values(AUDIT_TYPE).every(filterKey => filters[filterKey])) {
+          filters[FILTERS.ALL] = true;
+        } else if (!isToggledToActive && filters[FILTERS.ALL]) {
+          filters[FILTERS.ALL] = false;
+        }
+      }
 
       return {
         ...state,
         filters,
       };
     case AUDITING:
       const { auditing } = action;
 
--- a/devtools/client/accessibility/test/browser/browser.ini
+++ b/devtools/client/accessibility/test/browser/browser.ini
@@ -15,16 +15,17 @@ support-files =
 skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
 [browser_accessibility_context_menu_inspector.js]
 skip-if = (os == 'win' && processor == 'aarch64') # bug 1533484
 [browser_accessibility_mutations.js]
 skip-if = (os == 'win' && processor == 'aarch64') # bug 1533534
 [browser_accessibility_panel_highlighter.js]
 [browser_accessibility_panel_highlighter_multi_tab.js]
 skip-if = (os == 'linux' && debug && bits == 64) # Bug 1511247
+[browser_accessibility_panel_toolbar_checks.js]
 [browser_accessibility_relation_navigation.js]
 [browser_accessibility_reload.js]
 [browser_accessibility_sidebar_checks.js]
 [browser_accessibility_sidebar.js]
 [browser_accessibility_tree_audit_long.js]
 [browser_accessibility_tree_audit_reset.js]
 [browser_accessibility_tree_audit_toolbar.js]
 [browser_accessibility_tree_audit.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_panel_toolbar_checks.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global toggleFilter */
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body></body>
+</html>`;
+
+/**
+* Test data has the format of:
+* {
+*   desc     {String}    description for better logging
+*   setup    {Function}  An optional setup that needs to be performed before
+*                        the state of the tree and the sidebar can be checked.
+*   expected {JSON}      An expected states for the tree and the sidebar.
+* }
+*/
+const tests = [{
+  desc: "Check initial state.",
+  expected: {
+    toolbar: [false, false],
+  },
+}, {
+  desc: "Toggle first filter (all) to activate.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 0);
+  },
+  expected: {
+    toolbar: [true, true],
+  },
+}, {
+  desc: "Click on the filter again.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 0);
+  },
+  expected: {
+    toolbar: [false, false],
+  },
+}, {
+  desc: "Toggle second filter (all) (contrast) to activate.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 1);
+  },
+  expected: {
+    toolbar: [true, true],
+  },
+}, {
+  desc: "Click on the filter again.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 1);
+  },
+  expected: {
+    toolbar: [false, false],
+  },
+}, {
+  desc: "Toggle second filter (all) (contrast) to activate.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 1);
+  },
+  expected: {
+    toolbar: [true, true],
+  },
+}, {
+  desc: "Click on the first filter to de-activate all.",
+  setup: async ({ doc }) => {
+    await toggleFilter(doc, 0);
+  },
+  expected: {
+    toolbar: [false, false],
+  },
+}];
+
+/**
+* Simple test that checks toggle states for filters in the Accessibility panel
+* toolbar.
+*/
+addA11yPanelTestsTask(tests, TEST_URI,
+  "Test Accessibility panel filter toggle states.");
--- a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit_toolbar.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* global toggleRow, toggleFilter */
+/* global toggleFilter */
 
 const TEST_URI = `<html>
   <head>
     <meta charset="utf-8"/>
     <title>Accessibility Panel Test</title>
   </head>
   <body>
     <h1 style="color:rgba(255,0,0,0.1); background-color:rgba(255,255,255,1);">
@@ -32,33 +32,35 @@ const TEST_URI = `<html>
 const tests = [{
   desc: "Check initial state.",
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
       selected: true,
     }],
+    toolbar: [false, false],
   },
 }, {
-  desc: "Run an audit from a11y panel toolbar by activating a filter.",
+  desc: "Run an audit (all) from a11y panel toolbar by activating a filter.",
   setup: async ({ doc }) => {
     await toggleFilter(doc, 0);
   },
   expected: {
     tree: [{
       role: "text leaf",
       name: `"Top level header "contrast`,
       badges: [ "contrast" ],
       selected: true,
     }, {
       role: "text leaf",
       name: `"Second level header "contrast`,
       badges: [ "contrast" ],
     }],
+    toolbar: [true, true],
   },
 }, {
   desc: "Click on the filter again.",
   setup: async ({ doc }) => {
     await toggleFilter(doc, 0);
   },
   expected: {
     tree: [{
@@ -75,17 +77,18 @@ const tests = [{
     }, {
       role: "heading",
       name: `"Second level header"`,
     }, {
       role: "text leaf",
       name: `"Second level header "contrast`,
       badges: [ "contrast" ],
     }],
+    toolbar: [false, false],
   },
 }];
 
 /**
  * Simple test that checks content of the Accessibility panel tree when the
  * audit is activated via the panel's toolbar.
  */
 addA11yPanelTestsTask(tests, TEST_URI,
-  "Test Accessibility panel tree with contrast filter audit activation.");
+  "Test Accessibility panel tree with 'all' filter audit activation.");
--- a/devtools/client/accessibility/test/browser/head.js
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -376,16 +376,33 @@ async function checkSidebarState(store, 
     }
 
     ok(true, "Sidebar state is correct.");
     return true;
   });
 }
 
 /**
+ * Check the state of the accessibility checks toolbar.
+ * @param  {Object} store         React store for the panel (includes store for
+ *                                the sidebar).
+ * @param  {Object} expected      Expected active state of the filters in the
+ *                                toolbar.
+ */
+async function checkToolbarState(doc, expected) {
+  info("Checking toolbar state.");
+  const hasExpectedStructure = await BrowserTestUtils.waitForCondition(() =>
+    [...doc.querySelectorAll("button.toggle-button.badge")].every((filter, i) =>
+      expected[i] === filter.classList.contains("checked")),
+      "Wait for the right toolbar state.");
+
+  ok(hasExpectedStructure, "Toolbar state is correct.");
+}
+
+/**
  * Focus accessibility properties tree in the a11y inspector sidebar. If focused for the
  * first time, the tree will select first rendered node as defult selection for keyboard
  * purposes.
  *
  * @param  {Document} doc  accessibility inspector panel document.
  */
 async function focusAccessibleProperties(doc) {
   const tree = doc.querySelector(".tree");
@@ -509,25 +526,29 @@ async function selectAccessibleForNode(e
 async function runA11yPanelTests(tests, env) {
   for (const { desc, setup, expected } of tests) {
     info(desc);
 
     if (setup) {
       await setup(env);
     }
 
-    const { tree, sidebar, audit } = expected;
+    const { tree, sidebar, audit, toolbar } = expected;
     if (tree) {
       await checkTreeState(env.doc, tree);
     }
 
     if (sidebar) {
       await checkSidebarState(env.store, sidebar);
     }
 
+    if (toolbar) {
+      await checkToolbarState(env.doc, toolbar);
+    }
+
     if (typeof audit !== "undefined") {
       await checkAuditState(env.store, audit);
     }
   }
 }
 
 /**
  * Build a valid URL from an HTML snippet.
--- a/devtools/client/accessibility/test/jest/components/__snapshots__/accessibility-tree-filter.test.js.snap
+++ b/devtools/client/accessibility/test/jest/components/__snapshots__/accessibility-tree-filter.test.js.snap
@@ -1,11 +1,27 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`AccessibilityTreeFilter component: audit filter filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
-
 exports[`AccessibilityTreeFilter component: audit filter filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"true\\" class=\\"badge toggle-button checked devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: audit filter not filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filter not filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
 
 exports[`AccessibilityTreeFilter component: audit filter not filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: toggle filter 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filters filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.filter.all</button><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 2`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 3`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 4`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.filter.all</button><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 5`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 6`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 7`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: render filters after state changes 8`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.filter.all</button><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
+
+exports[`AccessibilityTreeFilter component: toggle filter 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.filter.all</button><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
--- a/devtools/client/accessibility/test/jest/components/accessibility-tree-filter.test.js
+++ b/devtools/client/accessibility/test/jest/components/accessibility-tree-filter.test.js
@@ -12,61 +12,100 @@ const { ToggleButton } = require("devtoo
 const ConnectedAccessibilityTreeFilterClass =
   require("devtools/client/accessibility/components/AccessibilityTreeFilter");
 const AccessibilityTreeFilterClass =
   ConnectedAccessibilityTreeFilterClass.WrappedComponent;
 const AccessibilityTreeFilter = createFactory(ConnectedAccessibilityTreeFilterClass);
 const {
   setupStore,
 } = require("devtools/client/accessibility/test/jest/helpers");
-const { FILTERS } = require("devtools/client/accessibility/constants");
+
+const { AUDIT, AUDITING, FILTERS, FILTER_TOGGLE } = require("devtools/client/accessibility/constants");
 const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
 
+function checkFilter(button, expectedText) {
+  expect(button.is("button")).toBe(true);
+  expect(button.hasClass("badge")).toBe(true);
+  expect(button.hasClass("toggle-button")).toBe(true);
+  expect(button.hasClass("audit-badge")).toBe(false);
+  expect(button.prop("aria-pressed")).toBe(false);
+  expect(button.text()).toBe(expectedText);
+}
+
+function checkToggleFilter(wrapper, filter) {
+  const filterInstance = wrapper.find(AccessibilityTreeFilterClass).instance();
+  filterInstance.toggleFilter = jest.fn();
+  filter.simulate("keydown", { key: " " });
+  expect(filterInstance.toggleFilter.mock.calls.length).toBe(1);
+
+  filter.simulate("keydown", { key: "Enter" });
+  expect(filterInstance.toggleFilter.mock.calls.length).toBe(2);
+
+  filter.simulate("click", { clientX: 1 });
+  expect(filterInstance.toggleFilter.mock.calls.length).toBe(3);
+}
+
+function checkFiltersState(wrapper, expected) {
+  const filters = wrapper.find(AccessibilityTreeFilterClass);
+  const filterButtons = filters.find(ToggleButton);
+
+  for (let i = 0; i < filterButtons.length; i++) {
+    const filter = filterButtons.at(i).childAt(0);
+    expect(filter.prop("aria-pressed")).toBe(expected[i].active);
+    expect(filter.prop("aria-busy")).toBe(expected[i].busy);
+  }
+}
+
 describe("AccessibilityTreeFilter component:", () => {
   it("audit filter not filtered", () => {
     const store = setupStore();
 
     const wrapper = mount(Provider({store}, AccessibilityTreeFilter()));
     expect(wrapper.html()).toMatchSnapshot();
 
     const filters = wrapper.find(AccessibilityTreeFilterClass);
     expect(filters.children().length).toBe(1);
 
     const toolbar = filters.childAt(0);
     expect(toolbar.is("div")).toBe(true);
     expect(toolbar.prop("role")).toBe("toolbar");
 
     const filterButtons = filters.find(ToggleButton);
-    expect(filterButtons.length).toBe(1);
+    expect(filterButtons.length).toBe(2);
 
-    const button = filterButtons.at(0).childAt(0);
-    expect(button.is("button")).toBe(true);
-    expect(button.hasClass("badge")).toBe(true);
-    expect(button.hasClass("toggle-button")).toBe(true);
-    expect(button.hasClass("audit-badge")).toBe(false);
-    expect(button.prop("aria-pressed")).toBe(false);
-    expect(button.text()).toBe("accessibility.badge.contrast");
+    const expectedText = [
+      "accessibility.filter.all",
+      "accessibility.badge.contrast",
+    ];
+
+    for (let i = 0; i < filterButtons.length; i++) {
+      checkFilter(filterButtons.at(i).childAt(0), expectedText[i]);
+    }
   });
 
-  it("audit filter filtered", () => {
+  it("audit filters filtered", () => {
     const store = setupStore({
       preloadedState: { audit: {
         filters: {
+          [FILTERS.ALL]: true,
           [FILTERS.CONTRAST]: true,
         },
         auditing: [],
       }},
     });
 
     const wrapper = mount(Provider({store}, AccessibilityTreeFilter()));
     expect(wrapper.html()).toMatchSnapshot();
 
-    const button = wrapper.find("button");
-    expect(button.prop("aria-pressed")).toBe(true);
-    expect(button.hasClass("checked")).toBe(true);
+    const buttons = wrapper.find("button");
+    for (let i = 0; i < buttons.length; i++) {
+      const button = buttons.at(i);
+      expect(button.prop("aria-pressed")).toBe(true);
+      expect(button.hasClass("checked")).toBe(true);
+    }
   });
 
   it("audit filter not filtered auditing", () => {
     const store = setupStore({
       preloadedState: { audit: {
         filters: {
           [FILTERS.CONTRAST]: false,
         },
@@ -104,20 +143,118 @@ describe("AccessibilityTreeFilter compon
     expect(button.hasClass("devtools-throbber")).toBe(true);
   });
 
   it("toggle filter", () => {
     const store = setupStore();
     const wrapper = mount(Provider({store}, AccessibilityTreeFilter()));
     expect(wrapper.html()).toMatchSnapshot();
 
-    const filterInstance = wrapper.find(AccessibilityTreeFilterClass).instance();
-    filterInstance.toggleFilter = jest.fn();
-    wrapper.find("button.toggle-button.badge").simulate("keydown", { key: " " });
-    expect(filterInstance.toggleFilter.mock.calls.length).toBe(1);
+    const filters = wrapper.find("button.toggle-button.badge");
+    for (let i = 0; i < filters.length; i++) {
+      const filter = filters.at(i);
+      checkToggleFilter(wrapper, filter);
+    }
+  });
+
+  it("render filters after state changes", () => {
+    const store = setupStore();
+
+    const wrapper = mount(Provider({store}, AccessibilityTreeFilter()));
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: false,
+    }, {
+      active: false, busy: false,
+    }]);
+
+    store.dispatch({
+      type: AUDITING,
+      auditing: Object.values(FILTERS),
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: true,
+    }, {
+      active: false, busy: true,
+    }]);
+
+    store.dispatch({
+      type: AUDIT,
+      response: [],
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: false,
+    }, {
+      active: false, busy: false,
+    }]);
+
+    store.dispatch({
+      type: FILTER_TOGGLE,
+      filter: FILTERS.ALL,
+    });
+    wrapper.update();
 
-    wrapper.find("button.toggle-button.badge").simulate("keydown", { key: "Enter" });
-    expect(filterInstance.toggleFilter.mock.calls.length).toBe(2);
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: true, busy: false,
+    }, {
+      active: true, busy: false,
+    }]);
+
+    store.dispatch({
+      type: FILTER_TOGGLE,
+      filter: FILTERS.CONTRAST,
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: false,
+    }, {
+      active: false, busy: false,
+    }]);
+
+    store.dispatch({
+      type: AUDITING,
+      auditing: [FILTERS.CONTRAST],
+    });
+    wrapper.update();
 
-    wrapper.find("button.toggle-button.badge").simulate("click", { clientX: 1 });
-    expect(filterInstance.toggleFilter.mock.calls.length).toBe(3);
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: false,
+    }, {
+      active: false, busy: true,
+    }]);
+
+    store.dispatch({
+      type: AUDIT,
+      response: [],
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: false, busy: false,
+    }, {
+      active: false, busy: false,
+    }]);
+
+    store.dispatch({
+      type: FILTER_TOGGLE,
+      filter: FILTERS.CONTRAST,
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    checkFiltersState(wrapper, [{
+      active: true, busy: false,
+    }, {
+      active: true, busy: false,
+    }]);
   });
 });
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -152,16 +152,21 @@ accessibility.contrast.annotation.AAA=Me
 # run time with the accessibility.learnMore string.
 accessibility.contrast.annotation.fail=Does not meet WCAG standards for accessible text. %S
 
 # LOCALIZATION NOTE (accessibility.badges): A title text for the group of badges
 # that are rendered for each accessible row within the accessibility tree when
 # one or more accessibility checks fail.
 accessibility.badges=Accessibility checks
 
+# LOCALIZATION NOTE (accessibility.filter.all): A title text for the filter
+# that is rendered within the accessibility panel toolbar for a button that
+# filters the tree based on all accessibility failures within it.
+accessibility.filter.all=all
+
 # LOCALIZATION NOTE (accessibility.badge.contrast): A title text for the badge
 # that is rendered within the accessible row in the accessibility tree for a
 # given accessible object that does not satisfy the WCAG guideline for colour
 # contrast.
 accessibility.badge.contrast=contrast
 
 # LOCALIZATION NOTE (accessibility.badge.contrast.warning): A label for the
 # badge and attached warning icon that is rendered within the accessible row in