diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx index 0b17f009..c6ed438a 100644 --- a/src/components/HomepageFeatures/index.tsx +++ b/src/components/HomepageFeatures/index.tsx @@ -17,7 +17,9 @@ const FeatureList: FeatureItem[] = [ title: "Written in Rust", Svg: new_logo_black, description: ( - <>Boa brings Rust's memory safety guarantees to the world of JS engines. + <> + Boa brings Rust's memory safety guarantees to the world of JS engines. + ), }, { diff --git a/src/components/benchmarks/index.tsx b/src/components/benchmarks/index.tsx index a028f0fa..8bc4f2c7 100644 --- a/src/components/benchmarks/index.tsx +++ b/src/components/benchmarks/index.tsx @@ -25,7 +25,7 @@ ChartJS.register( Tooltip, Legend, Colors, - BarElement + BarElement, ); export function BenchmarkGraphs() { @@ -52,7 +52,7 @@ export function BenchmarkGraphs() { const buildChartFromBenchmark = async (name: string) => { const data = await fetchData( - `https://raw.githubusercontent.com/boa-dev/data/main/bench/results/${name}.json` + `https://raw.githubusercontent.com/boa-dev/data/main/bench/results/${name}.json`, ); const barData = getBarChartData(data); @@ -79,7 +79,7 @@ const fetchData = async (url: string) => { const data = await response.json(); return { labels: data.labels.map((epoch: number) => - new Date(epoch).toLocaleDateString() + new Date(epoch).toLocaleDateString(), ), datasets: [ { diff --git a/src/components/conformance/Banner/index.tsx b/src/components/conformance/HeroBanner/index.tsx similarity index 77% rename from src/components/conformance/Banner/index.tsx rename to src/components/conformance/HeroBanner/index.tsx index bc3bf1da..5fcfaf6c 100644 --- a/src/components/conformance/Banner/index.tsx +++ b/src/components/conformance/HeroBanner/index.tsx @@ -1,26 +1,27 @@ import React from "react"; -import { VersionItem, TestStats } from "@site/src/components/conformance/types"; -import { mapToTestStats } from "@site/src/components/conformance/utils"; +import { + VersionItem, + TestStats, + ConformanceState, +} from "@site/src/components/conformance/types"; +import { + createState, + mapToTestStats, +} from "@site/src/components/conformance/utils"; +import { useHistory } from "@docusaurus/router"; import Heading from "@theme/Heading"; import styles from "./styles.module.css"; interface BannerProps { focusItems: VersionItem[]; - setNewVersion: (newVersion: VersionItem) => void; } -export default function ConformanceBanner(props: BannerProps): JSX.Element { +export default function ConformanceHeroBanner(props: BannerProps): JSX.Element { return (
{props.focusItems.map((item) => { - return ( - - ); + return ; })}
); @@ -29,6 +30,8 @@ export default function ConformanceBanner(props: BannerProps): JSX.Element { function BannerCard(props) { const [stats, setStats] = React.useState(null); + const history = useHistory(); + React.useEffect(() => { const fetchStats = async () => { const response = await fetch(props.item.fetchUrl); @@ -81,7 +84,12 @@ function BannerCard(props) {
diff --git a/src/components/conformance/Banner/styles.module.css b/src/components/conformance/HeroBanner/styles.module.css similarity index 100% rename from src/components/conformance/Banner/styles.module.css rename to src/components/conformance/HeroBanner/styles.module.css diff --git a/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/cards/TestGrid/index.tsx b/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/cards/TestGrid/index.tsx index bbda9e43..2c7c6aaa 100644 --- a/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/cards/TestGrid/index.tsx +++ b/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/cards/TestGrid/index.tsx @@ -4,13 +4,14 @@ import { TestOutcome, TestResult, SuiteResult, + ConformanceState, } from "@site/src/components/conformance/types"; import Heading from "@theme/Heading"; import styles from "./styles.module.css"; type TestsGridProps = { + state: ConformanceState; suite: SuiteResult; - esFlag: string | null; selectTest: (string) => void; }; @@ -26,7 +27,7 @@ export default function TestsGrid(props: TestsGridProps): JSX.Element {
diff --git a/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/index.tsx b/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/index.tsx index 185a1714..75068a6e 100644 --- a/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/index.tsx +++ b/src/components/conformance/ResultsDisplay/components/SuiteDataContainer/index.tsx @@ -1,39 +1,36 @@ import React from "react"; import TestsGrid from "./cards/TestGrid"; import TestViewer from "./cards/TestViewer"; -import { SuiteResult } from "@site/src/components/conformance/types"; +import { + ConformanceState, + SuiteResult, +} from "@site/src/components/conformance/types"; import styles from "./styles.module.css"; type SuiteDataProps = { suite: SuiteResult; - esFlag: string | null; + state: ConformanceState; t262Path: string; + setSelectedTest: (string) => void; }; export default function SuiteDataContainer(props: SuiteDataProps): JSX.Element { - const [selectedTest, setSelectedTest] = React.useState(null); - - // Unselect a test if the underlying test262 path has been changed. - React.useEffect(() => { - setSelectedTest(null); - }, [props.t262Path]); - // Set the user's selected test to be displayed in the ViewPort. const selectTest = (testName: string) => { - setSelectedTest(testName); + props.setSelectedTest(testName); }; const clearTest = () => { - setSelectedTest(null); + props.setSelectedTest(undefined); }; // Add a TestViewer to look up and display the test262. return (
- {selectedTest ? ( + {props.state.selectedTest ? ( clearTest()} /> diff --git a/src/components/conformance/ResultsDisplay/components/SuiteDisplay/index.tsx b/src/components/conformance/ResultsDisplay/components/SuiteDisplay/index.tsx index 4127c3c8..141614af 100644 --- a/src/components/conformance/ResultsDisplay/components/SuiteDisplay/index.tsx +++ b/src/components/conformance/ResultsDisplay/components/SuiteDisplay/index.tsx @@ -1,15 +1,19 @@ import React from "react"; import SuiteSelector from "../SuiteSelector"; import SuiteDataContainer from "../SuiteDataContainer"; -import { SuiteResult } from "@site/src/components/conformance/types"; +import { + ConformanceState, + SuiteResult, +} from "@site/src/components/conformance/types"; import styles from "./styles.module.css"; type SuiteDisplayProps = { currentSuite: SuiteResult; - esFlag: string | null; + state: ConformanceState; t262Path: string; navigateToSuite: (string) => void; + setSelectedTest: (string) => void; }; export default function SuiteDisplay(props: SuiteDisplayProps): JSX.Element { @@ -18,15 +22,16 @@ export default function SuiteDisplay(props: SuiteDisplayProps): JSX.Element { {props.currentSuite.suites ? ( ) : null} {props.currentSuite.tests ? ( ) : null}
diff --git a/src/components/conformance/ResultsDisplay/components/SuiteSelector/index.tsx b/src/components/conformance/ResultsDisplay/components/SuiteSelector/index.tsx index cc19e598..bf327525 100644 --- a/src/components/conformance/ResultsDisplay/components/SuiteSelector/index.tsx +++ b/src/components/conformance/ResultsDisplay/components/SuiteSelector/index.tsx @@ -12,16 +12,18 @@ type SelectorProps = { export default function SuiteSelector(props: SelectorProps): JSX.Element { return (
- {props.suites.map((suite) => { - return ( - - ); - })} + {props.suites + .sort((a, b) => a.name.localeCompare(b.name)) + .map((suite) => { + return ( + + ); + })}
); } diff --git a/src/components/conformance/ResultsDisplay/index.tsx b/src/components/conformance/ResultsDisplay/index.tsx index 573f6dba..45a3b1e2 100644 --- a/src/components/conformance/ResultsDisplay/index.tsx +++ b/src/components/conformance/ResultsDisplay/index.tsx @@ -4,46 +4,70 @@ import { ResultInfo, VersionItem, SuiteResult, - SpecEdition, + ConformanceState, } from "@site/src/components/conformance/types"; -import { mapToResultInfo } from "@site/src/components/conformance/utils"; +import ResultNavigation from "./nav"; +import { + createState, + mapToResultInfo, +} from "@site/src/components/conformance/utils"; +import { useHistory } from "@docusaurus/router"; import styles from "./styles.module.css"; type ResultsProps = { - activeVersion: VersionItem; + state: ConformanceState; }; export default function ResultsDisplay(props: ResultsProps): JSX.Element { - const [activeResults, setActiveResults] = React.useState( - null, - ); - const [testPath, setTestPath] = React.useState([ - props.activeVersion.tagName, - ]); const [currentSuite, setCurrentSuite] = React.useState( null, ); - const [esVersionFlag, setEsVersionFlag] = React.useState(null); - React.useEffect(() => { - fetchResults(props.activeVersion).then((data) => { - const resultInfo = mapToResultInfo(data); - setActiveResults(resultInfo); - setCurrentSuite(resultInfo.results); - }); + // Refs + const activeResults = React.useRef(); - setTestPath([props.activeVersion.tagName]); - }, [props.activeVersion]); + const history = useHistory(); React.useEffect(() => { - // Return early if activeResults is null. - if (!activeResults) return; + // If the version is correctly synced + if (props.state.version.tagName !== activeResults.current?.version) { + updateActiveResults().then((results) => { + activeResults.current = results; + const foundSuite = findResultsFromPath(results); + setCurrentSuite(foundSuite); + }); + // Return to prevent further execution. + return; + } + + // TODO / NOTE: there may be a bug with version swapping. TBD. + // Return early if for some reason activeResults.current is undefined + if (!activeResults.current) return; + + // Results should be defined from the check on activeResults.current + const foundSuite = findResultsFromPath(activeResults.current); + setCurrentSuite(foundSuite); + }, [props.state]); + + // Fetches the version results + const fetchResults = async (version: VersionItem) => { + const response = await fetch(version.fetchUrl); + return await response.json(); + }; + + const updateActiveResults = async (): Promise => { + const data = await fetchResults(props.state.version); + return mapToResultInfo(props.state.version.tagName, data); + // setCurrentSuite(resultInfo.results); + }; - let newSuiteTarget: SuiteResult | null = null; - for (const target of testPath) { - if (target === props.activeVersion.tagName) { - newSuiteTarget = activeResults.results; + const findResultsFromPath = (activeResultsInfo: ResultInfo): SuiteResult => { + let newSuiteTarget: SuiteResult | undefined = undefined; + for (const target of props.state.testPath) { + if (target === props.state.version.tagName) { + newSuiteTarget = activeResultsInfo.results; + continue; } // Suites must exist here for the path value to be valid. @@ -53,142 +77,85 @@ export default function ResultsDisplay(props: ResultsProps): JSX.Element { } } } - - setCurrentSuite(newSuiteTarget); - }, [testPath]); - - // Fetches the version results - const fetchResults = async (version: VersionItem) => { - const response = await fetch(version.fetchUrl); - return await response.json(); + return newSuiteTarget; }; // Navigates to a suite by adding the SuiteName to the test path array. const navigateToSuite = (newSuiteName: string) => { - setTestPath((testPath) => [...testPath, newSuiteName]); + const newPath = [...props.state.testPath, newSuiteName]; + history.push({ + pathname: "/conformance", + state: createState( + props.state.version, + newPath, + props.state.ecmaScriptVersion, + ), + }); }; // Removes a value or values from the test path array. // // Used by breadcrumbs for navigation. const sliceNavToIndex = (nonInclusiveIndex: number) => { - setTestPath((testPath) => [...testPath.slice(0, nonInclusiveIndex)]); + const slicedPath = [...props.state.testPath.slice(0, nonInclusiveIndex)]; + history.push({ + pathname: "/conformance", + state: createState( + props.state.version, + slicedPath, + props.state.ecmaScriptVersion, + ), + }); }; // Sets the ECMAScript version flag value. const setEcmaScriptFlag = (flag: string) => { - const nulledFlag = flag ? flag : null; - setEsVersionFlag(nulledFlag); + const nulledFlag = flag ? flag : undefined; + history.push({ + pathname: "/conformance", + state: createState(props.state.version, props.state.testPath, nulledFlag), + }); + }; + + // Sets a selected test. + const setSelectedTest = (test: string | undefined) => { + history.push({ + pathname: "/conformance", + state: createState( + props.state.version, + props.state.testPath, + props.state.ecmaScriptVersion, + test, + ), + }); }; // Create the t262 URL from testPath with the results commit const t262Path = (): string => { // NOTE: testPath[0] === activeBoaReleaseTag return [ - activeResults.test262Commit, + activeResults.current.test262Commit, "test", - ...testPath.slice(1, testPath.length), + ...props.state.testPath.slice(1, props.state.testPath.length), ].join("/"); }; return (
- {activeResults && currentSuite ? ( + {currentSuite ? ( setSelectedTest(test)} /> ) : null}
); } - -type ResultsNavProps = { - navPath: string[]; - sliceNavToIndex: (number) => void; - setEcmaScriptFlag: (string) => void; -}; - -function ResultNavigation(props: ResultsNavProps): JSX.Element { - return ( -
- - -
- ); -} - -type NavItemProps = { - itemName: string; - index: number; - breadcrumbValue: string; - sliceNavToIndex: (number) => void; -}; - -function NavItem(props: NavItemProps): JSX.Element { - return ( -
  • - props.sliceNavToIndex(props.index + 1)} - > - {props.itemName} - -
  • - ); -} - -type DropDownProps = { - setEcmaScriptFlag: (string) => void; -}; - -function EcmaScriptVersionDropdown(props: DropDownProps): JSX.Element { - const [dropdownValue, setDropdownValue] = React.useState(""); - - const handleVersionSelection = (e) => { - setDropdownValue(e.target.value); - props.setEcmaScriptFlag(e.target.value); - }; - - return ( -
    - -
    - ); -} diff --git a/src/components/conformance/ResultsDisplay/nav.tsx b/src/components/conformance/ResultsDisplay/nav.tsx new file mode 100644 index 00000000..abe4814e --- /dev/null +++ b/src/components/conformance/ResultsDisplay/nav.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import { ConformanceState, SpecEdition } from "../types"; + +import styles from "./styles.module.css"; +import Link from "@docusaurus/Link"; + +type ResultsNavProps = { + state: ConformanceState; + sliceNavToIndex: (number) => void; + setEcmaScriptFlag: (string) => void; +}; + +export default function ResultNavigation(props: ResultsNavProps): JSX.Element { + return ( +
    + + +
    + ); +} + +type BreadCrumbProps = { + navPath: string[]; + sliceNavToIndex: (number) => void; +}; + +function NavBreadCrumbs(props: BreadCrumbProps) { + return ( + + ); +} + +type NavItemProps = { + itemName: string; + index: number; + breadcrumbValue: string; + sliceNavToIndex: (number) => void; +}; + +function NavItem(props: NavItemProps): JSX.Element { + return ( +
  • + props.sliceNavToIndex(props.index + 1)} + > + {props.itemName} + +
  • + ); +} + +// Below implements the ECMAScript Version Dropdown component + +type DropDownProps = { + esVersionValue: string; + setEcmaScriptFlag: (string) => void; +}; + +function EcmaScriptVersionDropdown(props: DropDownProps): JSX.Element { + const [dropdownValue, setDropdownValue] = React.useState(props.esVersionValue ? props.esVersionValue : ""); + + // Update the flag when props.esVersionValue is changed + React.useEffect(()=>{ + setDropdownValue(props.esVersionValue) + }, [props.esVersionValue]) + + const handleVersionSelection = (e) => { + // Update the display value and set the flag. + setDropdownValue(e.target.value); + props.setEcmaScriptFlag(e.target.value); + }; + + return ( +
    + +
    + ); +} diff --git a/src/components/conformance/VersionSelector/index.tsx b/src/components/conformance/VersionSelector/index.tsx index 692d65ed..8d7323e3 100644 --- a/src/components/conformance/VersionSelector/index.tsx +++ b/src/components/conformance/VersionSelector/index.tsx @@ -1,29 +1,33 @@ import React from "react"; -import { VersionItem } from "@site/src/components/conformance/types"; +import { useHistory } from "@docusaurus/router"; +import { + ConformanceState, + VersionItem, +} from "@site/src/components/conformance/types"; import styles from "./styles.module.css"; +import { createState } from "../utils"; interface SelectorProps { availableVersions: VersionItem[]; - setNewVersion: (newVersion: VersionItem) => void; } export default function VersionSelector(props: SelectorProps): JSX.Element { return (
    {props.availableVersions.map((version) => { - return ( - - ); + return ; })}
    ); } -function Version(props): JSX.Element { +type VersionProps = { + version: VersionItem; +}; + +function Version(props: VersionProps): JSX.Element { + const history = useHistory(); + return (
    @@ -31,8 +35,12 @@ function Version(props): JSX.Element {
    diff --git a/src/components/conformance/index.tsx b/src/components/conformance/index.tsx new file mode 100644 index 00000000..ec1c16f6 --- /dev/null +++ b/src/components/conformance/index.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import ConformanceHeroBanner from "@site/src/components/conformance/HeroBanner"; +import ResultsDisplay from "@site/src/components/conformance/ResultsDisplay"; +import VersionSelector from "@site/src/components/conformance/VersionSelector"; +import { VersionItem, ConformanceState } from "./types"; + +type ViewProps = { + state: undefined | ConformanceState; + records: VersionItem[]; +}; + +export default function ConformanceView(props: ViewProps): JSX.Element { + return ( + <> + + {props.state ? ( + + ) : ( + + )} + + ); +} diff --git a/src/components/conformance/types.ts b/src/components/conformance/types.ts index 975ddebf..fa710783 100644 --- a/src/components/conformance/types.ts +++ b/src/components/conformance/types.ts @@ -1,3 +1,14 @@ +// The main global state of the conformance page. +// +// This state is fed into the History object and dictates +// what renders. +export type ConformanceState = { + version: VersionItem; + testPath: string[]; + ecmaScriptVersion: string | undefined; + selectedTest: string | undefined; +}; + export type VersionItem = { tagName: string; fetchUrl: string; @@ -11,6 +22,7 @@ export type TestStats = { }; export type ResultInfo = { + version: string; commit: string; test262Commit: string; results: SuiteResult; diff --git a/src/components/conformance/utils.ts b/src/components/conformance/utils.ts index 29aef98d..2c106e15 100644 --- a/src/components/conformance/utils.ts +++ b/src/components/conformance/utils.ts @@ -1,4 +1,5 @@ import { + ConformanceState, ResultInfo, SpecEdition, SuiteResult, @@ -6,8 +7,25 @@ import { TestResult, TestStats, VersionedStats, + VersionItem, } from "@site/src/components/conformance/types"; +// Creates the state object from provided args +export function createState( + version: VersionItem, + testPath?: string[], + ecmaScriptVersion?: string, + selectedTest?: string, +): ConformanceState { + testPath = testPath ? testPath : [version.tagName]; + return { + version, + testPath, + ecmaScriptVersion, + selectedTest, + }; +} + // Interface for the http response of boa_tester's `ResultInfo` interface HttpResultInfo { c: string; @@ -16,8 +34,12 @@ interface HttpResultInfo { } // Function for converting an http response of boa_tester's `ResultInfo` to ResultInfo -export function mapToResultInfo(unmappedValue: HttpResultInfo): ResultInfo { +export function mapToResultInfo( + version: string, + unmappedValue: HttpResultInfo, +): ResultInfo { return { + version, commit: unmappedValue.c, test262Commit: unmappedValue.u, results: mapToSuiteResult(unmappedValue.r), diff --git a/src/components/latestPosts/latestPosts.tsx b/src/components/latestPosts/latestPosts.tsx index 29ed3f84..765c347c 100644 --- a/src/components/latestPosts/latestPosts.tsx +++ b/src/components/latestPosts/latestPosts.tsx @@ -30,7 +30,7 @@ export default function LatestPosts({ recentPosts }) { year: "numeric", month: "long", day: "numeric", - } + }, )}

    diff --git a/src/pages/conformance/index.tsx b/src/pages/conformance/index.tsx index 8e6bcbc4..f9c7fb85 100644 --- a/src/pages/conformance/index.tsx +++ b/src/pages/conformance/index.tsx @@ -1,7 +1,9 @@ -import ConformanceBanner from "@site/src/components/conformance/Banner"; -import ResultsDisplay from "@site/src/components/conformance/ResultsDisplay"; -import VersionSelector from "@site/src/components/conformance/VersionSelector"; -import { VersionItem } from "@site/src/components/conformance/types"; +import ConformanceView from "@site/src/components/conformance"; +import { + VersionItem, + ConformanceState, +} from "@site/src/components/conformance/types"; +import { useLocation } from "@docusaurus/router"; import Layout from "@theme/Layout"; import React from "react"; @@ -9,11 +11,12 @@ import styles from "./styles.module.css"; // TODO: Add header file to speed up statisic fetching for initial render? export default function Conformance() { - const [version, setVersion] = React.useState(null); + const location = useLocation(); const [releaseRecords, setReleaseRecords] = React.useState< - VersionItem[] | null + VersionItem[] | undefined >(null); + // Initial Render useEffect React.useEffect(() => { const validateReleaseVersion = (releaseTag: string) => { const version = releaseTag.split("."); @@ -25,12 +28,12 @@ export default function Conformance() { const fetchReleases = async () => { const response = await fetch( - "https://api.github.com/repos/boa-dev/boa/releases" + "https://api.github.com/repos/boa-dev/boa/releases", ); const releases = await response.json(); return releases .filter((potentialRelease) => - validateReleaseVersion(potentialRelease.tag_name) + validateReleaseVersion(potentialRelease.tag_name), ) .map((release) => { return { @@ -46,32 +49,15 @@ export default function Conformance() { }; fetchReleases().then((releases) => - setReleaseRecords([mainVersion, ...releases]) + setReleaseRecords([mainVersion, ...releases]), ); }, []); - const setNewVersion = (newVersion: VersionItem) => { - setVersion(newVersion); - }; - return (
    {releaseRecords ? ( - - ) : null} - {releaseRecords ? ( - version ? ( - - ) : ( - - ) + ) : null}