From 656ea5b5c454a1adb5397de0163c9f82391985bc Mon Sep 17 00:00:00 2001 From: atanasster Date: Sun, 17 May 2020 14:14:57 -0400 Subject: [PATCH] feat: use React.Profiler to render story --- .../src/components/AllyDashboard.tsx | 12 +- .../src/components/BaseAllyBlock.tsx | 119 ++++++------------ .../src/components/SelectionContext.tsx | 15 ++- plugins/axe-plugin/src/index.tsx | 46 ++++++- ui/blocks/src/context/block/BlockContext.tsx | 33 ++--- ui/blocks/src/context/block/ErrorBoundary.tsx | 21 ++++ 6 files changed, 139 insertions(+), 107 deletions(-) create mode 100644 ui/blocks/src/context/block/ErrorBoundary.tsx diff --git a/plugins/axe-plugin/src/components/AllyDashboard.tsx b/plugins/axe-plugin/src/components/AllyDashboard.tsx index a25a58531..f5b4b004c 100644 --- a/plugins/axe-plugin/src/components/AllyDashboard.tsx +++ b/plugins/axe-plugin/src/components/AllyDashboard.tsx @@ -4,7 +4,8 @@ import { AxeResults } from 'axe-core'; import { Chart } from 'react-google-charts'; import { Flex, Heading, Grid, Label, Checkbox } from 'theme-ui'; import { - axeResults, + axePasses, + axeViolations, isTagSelected, selectionList, taggedList, @@ -13,7 +14,7 @@ import { type StatsStatus = 'passes' | 'violations'; const TagSelectionCheckbox: FC<{ tag: string }> = ({ tag }) => { - const isSelected = tag ? useRecoilValue(isTagSelected(tag)) : false; + const isSelected = useRecoilValue(isTagSelected(tag)); const tagged = useRecoilValue(taggedList); const [selection, setSelection] = useRecoilState(selectionList); const toggleTagSelected = (tag: string) => { @@ -72,7 +73,12 @@ const collectStats = ( return success; }; export const AllyDashboard: FC = () => { - const results = useRecoilValue(axeResults); + const passes = useRecoilValue(axePasses); + const violations = useRecoilValue(axeViolations); + const results = { + violations, + passes, + }; const stats = collectStats( results, 'violations', diff --git a/plugins/axe-plugin/src/components/BaseAllyBlock.tsx b/plugins/axe-plugin/src/components/BaseAllyBlock.tsx index dec646216..abd56335a 100644 --- a/plugins/axe-plugin/src/components/BaseAllyBlock.tsx +++ b/plugins/axe-plugin/src/components/BaseAllyBlock.tsx @@ -1,21 +1,9 @@ /* eslint-disable react/display-name */ -import React, { FC, useRef, useEffect, useMemo } from 'react'; +import React, { FC, useEffect } from 'react'; import { useRecoilState } from 'recoil'; -import { - run as runAxe, - cleanup as cleanupAxe, - configure as configureAxe, - reset, - Spec, -} from 'axe-core'; +import { Spec, cleanup } from 'axe-core'; -import { Story } from '@component-controls/blocks'; - -import { - PanelContainer, - ActionItems, - resetTabCounter, -} from '@component-controls/components'; +import { PanelContainer, ActionItems } from '@component-controls/components'; import { ViolationsTable, PassesTable, IncompleteTable } from './ResultsTable'; import { HighlightSelector } from './HighlightSelector'; import { axeResults } from './SelectionContext'; @@ -25,91 +13,62 @@ import { AllyDashboard } from './AllyDashboard'; * options */ export interface BaseAllyBlockProps { - storyId?: string; options?: Spec; } /** * Displays the [axe](https://github.com/dequelabs/axe-core) ally test results */ -export const BaseAllyBlock: FC = ({ - storyId, - options = {}, -}) => { - const storyRef = useRef(null); - const isMounted = useRef(false); - const isRunning = useRef(false); - const [results, setResults] = useRecoilState(axeResults); +export const BaseAllyBlock: FC = ({ children }) => { + const [{ violations, passes, incomplete }] = useRecoilState(axeResults); + useEffect(() => { - resetTabCounter(); - reset(); - configureAxe(options); - isMounted.current = true; - if (isRunning.current === false) { - isRunning.current = true; - const el = storyRef.current?.firstChild; - runAxe(el ?? undefined) - .then(r => { - isRunning.current = false; - if (isMounted.current) { - setResults(r); - } - }) - .catch(e => { - isRunning.current = false; - console.error(e); - }); - } return () => { - isMounted.current = false; - cleanupAxe(); + cleanup(); }; - }, [storyId, storyRef.current]); - const actions: ActionItems = useMemo(() => { - const actions: ActionItems = [ - { - title: `dashboard`, - id: 'dashboard', - 'aria-label': 'display the accessibility overview dashboard', - panel: , - }, - ]; - if (results?.violations?.length) { - actions.push({ - title: `errors (${results.violations.length})`, - id: 'errors', - group: 'results', - 'aria-label': 'display the accessibility violations', - panel: , - }); - } - if (results?.passes?.length) { - actions.push({ - title: `passed (${results?.passes?.length})`, - id: 'passed', - group: 'results', - 'aria-label': 'display the accessibility successfully passed tests', - panel: , - }); - } - return actions; - }, [results]); - if (results?.incomplete?.length) { + }, []); + const actions: ActionItems = [ + { + title: `dashboard`, + id: 'dashboard', + 'aria-label': 'display the accessibility overview dashboard', + panel: , + }, + ]; + if (violations?.length) { + actions.push({ + title: `errors (${violations.length})`, + id: 'errors', + group: 'results', + 'aria-label': 'display the accessibility violations', + panel: , + }); + } + if (passes?.length) { actions.push({ - title: `incomplete (${results.incomplete.length})`, + title: `passed (${passes?.length})`, + id: 'passed', + group: 'results', + 'aria-label': 'display the accessibility successfully passed tests', + panel: , + }); + } + + if (incomplete?.length) { + actions.push({ + title: `incomplete (${incomplete.length})`, id: 'incomplete', group: 'results', 'aria-label': 'display the incomplete accessibility tests', panel: , }); } + // console.log(results); return ( - - - + {children} ); }; diff --git a/plugins/axe-plugin/src/components/SelectionContext.tsx b/plugins/axe-plugin/src/components/SelectionContext.tsx index 28bce7a6e..ec08486b9 100644 --- a/plugins/axe-plugin/src/components/SelectionContext.tsx +++ b/plugins/axe-plugin/src/components/SelectionContext.tsx @@ -66,20 +66,25 @@ export const selectionList = atom({ default: [], }); -export const isSelected = (selectionList: Selection) => +export const isSelected = (targets?: Selection) => selector({ - key: `isSelected_${selectionList.join('_')}`, + key: `isSelected_${targets ? targets.join('_') : 'none'}`, get: ({ get }: any) => { const selection = get(selectionList); - return selectionList.some((s: string) => selection.includes(s)); + return targets + ? targets.some((s: string) => selection.includes(s)) + : false; }, }); -export const isTagSelected = (tag: string) => +export const isTagSelected = (tag: string = '') => selector({ key: `isTagSelected_${tag}`, get: ({ get }: any) => { const tagged = get(taggedList); - return tagged[tag] ? isSelected(tagged[tag]) : false; + const selection = get(selectionList); + return tagged[tag] + ? tagged[tag].some((s: string) => selection.includes(s)) + : false; }, }); diff --git a/plugins/axe-plugin/src/index.tsx b/plugins/axe-plugin/src/index.tsx index 1b1e95362..4b6d5031f 100644 --- a/plugins/axe-plugin/src/index.tsx +++ b/plugins/axe-plugin/src/index.tsx @@ -1,13 +1,17 @@ /* eslint-disable react/display-name */ -import React, { FC } from 'react'; - +import React, { FC, useRef } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { run as runAxe, configure as configureAxe, reset } from 'axe-core'; +import { resetTabCounter } from '@component-controls/components'; import { StoryBlockContainer, StoryBlockContainerProps, + Story, } from '@component-controls/blocks'; import { Spec } from 'axe-core'; import { BaseAllyBlock } from './components/BaseAllyBlock'; +import { axeResults } from './components/SelectionContext'; interface AxeAllyBlockOwmProps { axeOptions?: Spec; @@ -22,10 +26,44 @@ export const AxeAllyBlock: FC = ({ axeOptions, ...props }) => { + const setResults = useSetRecoilState(axeResults); + const storyRef = useRef(null); + const isRunning = useRef(false); + const collectResults = () => { + const canvas = storyRef.current?.firstChild; + if (canvas && isRunning.current === false) { + isRunning.current = true; + reset(); + configureAxe(axeOptions || {}); + resetTabCounter(); + runAxe(canvas) + .then(results => { + setResults(results); + console.log(results); + isRunning.current = false; + }) + .catch(e => { + console.error('error running axe-core', e); + isRunning.current = false; + }); + } + }; + const onRender = () => { + collectResults(); + }; + return ( - {({ story }) => ( - + {({ story: { id: storyId } = {} }) => ( + <> + testing...}> + + + + + + + )} ); diff --git a/ui/blocks/src/context/block/BlockContext.tsx b/ui/blocks/src/context/block/BlockContext.tsx index e53ca9829..80eb09913 100644 --- a/ui/blocks/src/context/block/BlockContext.tsx +++ b/ui/blocks/src/context/block/BlockContext.tsx @@ -3,6 +3,7 @@ import { RecoilRoot } from 'recoil'; import { StoryStore } from '@component-controls/store'; import { BlockDataContextProvider } from './BlockDataContext'; import { BlockControlsContextProvider } from './BlockControlsContext'; +import { ErrorBoundary } from './ErrorBoundary'; export interface BlockContextInputProps { /** @@ -46,21 +47,23 @@ export const BlockContextProvider: React.FC = ({ options, }) => { return ( - - - - - {children} - - - - + + + + + + {children} + + + + + ); }; diff --git a/ui/blocks/src/context/block/ErrorBoundary.tsx b/ui/blocks/src/context/block/ErrorBoundary.tsx new file mode 100644 index 000000000..01e7318c1 --- /dev/null +++ b/ui/blocks/src/context/block/ErrorBoundary.tsx @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; + +export class ErrorBoundary extends Component { + state = { + error: undefined, + }; + componentDidCatch(error: Error | null, info: object) { + this.setState({ error: error || new Error('ERROR WAS NOT PROPAGATED') }); + console.error(error, info); + } + render() { + const { children } = this.props; + const { error } = this.state; + + if (error) { + return

Error caught.

; + } + + return children; + } +}