diff --git a/.gitignore b/.gitignore index f638a00..c956854 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +coverage dist/* *.tgz diff --git a/jest.config.ts b/jest.config.ts index 4e4eb0f..dea25d4 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,31 +1,13 @@ -import { Config } from "@jest/types" +import { Config } from "@jest/types"; -const common = { +const config: Config.InitialOptions = { testEnvironment: "jsdom", - transform: { + collectCoverage: true, + transform: { "^.+\\.(t)sx?$": "@swc/jest", }, setupFilesAfterEnv: ["src/test_setup.ts"], - testMatch: ["/src/**/*.(spec|test).ts?(x)"], -} - -const config: Config.InitialOptions = { - projects: [ - { - displayName: "history-v4", - moduleNameMapper: { - history$: "history-v4", - }, - ...common, - }, - { - displayName: "history-v5", - moduleNameMapper: { - history$: "history", - }, - ...common, - }, - ], -} + testMatch: ["/src/**/*.(spec|test).ts?(x)"], +}; -export default config +export default config; diff --git a/package.json b/package.json index 6aea61f..3bf4686 100644 --- a/package.json +++ b/package.json @@ -38,17 +38,17 @@ "homepage": "https://github.com/mitodl/course-search-utils#readme", "dependencies": { "@mitodl/open-api-axios": "^2024.5.6", - "@testing-library/react": "12", - "@testing-library/user-event": "^14.5.2", "axios": "^1.6.7", "fuse.js": "^7.0.0", "query-string": "^6.13.1", "ramda": "^0.27.1" }, "devDependencies": { - "@swc/core": "^1.3.0", - "@swc/jest": "^0.2.22", + "@swc/core": "^1.5.7", + "@swc/jest": "^0.2.36", + "@testing-library/react": "12", "@testing-library/react-hooks": "^8.0.1", + "@testing-library/user-event": "^14.5.2", "@types/enzyme": "^3.10.7", "@types/jest": "^29.0.1", "@types/lodash": "^4.14.162", @@ -80,7 +80,8 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "^6.22.2", - "ts-node": "^10.9.1", + "tiny-invariant": "^1.3.3", + "ts-node": "^10.9.2", "typescript": "^5.3.3" }, "peerDependencies": { diff --git a/src/facet_display/Facet.tsx b/src/facet_display/Facet.tsx index 63fa56d..6059f08 100644 --- a/src/facet_display/Facet.tsx +++ b/src/facet_display/Facet.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react" import { contains } from "ramda" import { SearchFacetItem } from "./SearchFacetItem" -import { BucketWithLabel } from "./FacetDisplay" +import { BucketWithLabel } from "./types" const MAX_DISPLAY_COUNT = 5 const FACET_COLLAPSE_THRESHOLD = 15 @@ -40,17 +40,17 @@ function SearchFacet(props: Props) { {showFacetList ? ( <> {results ? - results.map((facet, i) => + results.map((bucket, i) => showAllFacets || i < MAX_DISPLAY_COUNT || results.length < FACET_COLLAPSE_THRESHOLD ? ( ) : null ) : diff --git a/src/facet_display/FacetDisplay.test.tsx b/src/facet_display/FacetDisplay.test.tsx index 602fe75..07c6057 100644 --- a/src/facet_display/FacetDisplay.test.tsx +++ b/src/facet_display/FacetDisplay.test.tsx @@ -1,200 +1,296 @@ import React from "react" import { render, screen, within } from "@testing-library/react" import user from "@testing-library/user-event" +import invariant from "tiny-invariant" import FacetDisplay, { getDepartmentName } from "./FacetDisplay" import type { FacetDisplayProps } from "./FacetDisplay" -import { Bucket, FacetManifest, Facets } from "./types" +import { SingleFacetOptions, Facets, MultiFacetGroupOptions } from "./types" -describe("FacetDisplay component", () => { - const defaultFacetMap: FacetManifest = [ - { - name: "topic", - title: "Topics", - useFilterableFacet: false, - expandedOnLoad: true - }, - { - name: "department", - title: "Departments", - useFilterableFacet: false, - expandedOnLoad: true - } - ] +const topicFacet: SingleFacetOptions = { + name: "topic", + title: "Topics", + expandedOnLoad: true +} +const departmentFacet: SingleFacetOptions = { + name: "department", + title: "Departments", + expandedOnLoad: true +} - const defaultAggregations = { - topic: [ - { key: "Cats", doc_count: 10 }, - { key: "Dogs", doc_count: 20 }, - { key: "Monkeys", doc_count: 30 } - ], - department: [ - { key: "1", doc_count: 100 }, - { key: "2", doc_count: 200 } - ] - } +const setupFacetDisplay = ( + props: Partial & + Pick +) => { + const activeFacets = {} + const clearAllFilters = jest.fn() + const onFacetChange = jest.fn() - const setup = ({ - props, - aggregations = defaultAggregations - }: { - props?: Partial - aggregations?: Record - } = {}) => { - const activeFacets = {} - const facetOptions = (group: string) => aggregations[group] ?? [] - const clearAllFilters = jest.fn() - const onFacetChange = jest.fn() - - const view = render( + const view = render( + + ) + const spies = { + onFacetChange, + clearAllFilters + } + const rerender = (newProps: Partial) => { + view.rerender( ) - const spies = { - onFacetChange, - clearAllFilters - } - const rerender = (newProps: Partial) => { - view.rerender( - - ) - } - return { view, spies, rerender } } + return { view, spies, rerender } +} - test("Renders facets with expected titles", async () => { - setup() - screen.getByRole("button", { name: defaultFacetMap[0].title }) - screen.getByRole("button", { name: defaultFacetMap[1].title }) - }) +describe.each([undefined, "static", "filterable"] as const)( + "FacetDisplay component (type=%s)", + type => { + const defaultFacetManifest = [ + { ...topicFacet, type }, + { ...departmentFacet, type } + ] - test.each([{ expandedOnLoad: false }, { expandedOnLoad: true }])( - "Initially expanded based on expandedOnLoad ($expandedOnLoad)", - ({ expandedOnLoad }) => { - const facetMap = [{ ...defaultFacetMap[0], expandedOnLoad }] - setup({ - props: { facetMap } - }) - screen.getByRole("button", { - name: facetMap[0].title, - expanded: expandedOnLoad - }) - const checkboxes = screen.queryAllByRole("checkbox") - expect(checkboxes).toHaveLength(expandedOnLoad ? 3 : 0) + const defaultAggregations = { + topic: [ + { key: "Cats", doc_count: 10 }, + { key: "Dogs", doc_count: 20 }, + { key: "Donkeys", doc_count: 30 } + ], + department: [ + { key: "1", doc_count: 100 }, + { key: "2", doc_count: 200 } + ] } - ) + const setup = (props?: Partial) => + setupFacetDisplay({ + facetManifest: defaultFacetManifest, + facetOptions: defaultAggregations, + ...props + }) - test("Clicking facet title toggles expanded state", async () => { - setup({ - props: { facetMap: [defaultFacetMap[0]] } + test("Renders facets with expected titles", async () => { + setup() + screen.getByRole("button", { name: defaultFacetManifest[0].title }) + screen.getByRole("button", { name: defaultFacetManifest[1].title }) }) - const button = screen.getByRole("button", { - name: defaultFacetMap[0].title + + test.each([{ expandedOnLoad: false }, { expandedOnLoad: true }])( + "Initially expanded based on expandedOnLoad ($expandedOnLoad)", + ({ expandedOnLoad }) => { + const facetManifest = [{ ...defaultFacetManifest[0], expandedOnLoad }] + setup({ facetManifest }) + screen.getByRole("button", { + name: facetManifest[0].title, + expanded: expandedOnLoad + }) + const checkboxes = screen.queryAllByRole("checkbox") + expect(checkboxes).toHaveLength(expandedOnLoad ? 3 : 0) + } + ) + + test("Clicking facet title toggles expanded state", async () => { + setup({ facetManifest: [defaultFacetManifest[0]] }) + const button = screen.getByRole("button", { + name: defaultFacetManifest[0].title + }) + const checkboxCount = () => screen.queryAllByRole("checkbox").length + await user.click(button) + expect(button.getAttribute("aria-expanded")).toBe("false") + expect(checkboxCount()).toBe(0) + await user.click(button) + expect(button.getAttribute("aria-expanded")).toBe("true") + expect(checkboxCount()).toBe(3) }) - const checkboxCount = () => screen.queryAllByRole("checkbox").length - await user.click(button) - expect(button.getAttribute("aria-expanded")).toBe("false") - expect(checkboxCount()).toBe(0) - await user.click(button) - expect(button.getAttribute("aria-expanded")).toBe("true") - expect(checkboxCount()).toBe(3) - }) - test("Shows filters which are active", async () => { - const activeFacets: Facets = { - topic: ["Cats", "Dogs"], - department: ["1"] - } - const { spies } = setup({ props: { activeFacets } }) - const activeDisplay = document.querySelector(".active-search-filters") - if (!(activeDisplay instanceof HTMLElement)) { - throw new Error("Expected activeDisplay to exist") - } - within(activeDisplay).getByText("Cats") - within(activeDisplay).getByText("Dogs") - within(activeDisplay).getByText("1") - - await user.click(screen.getByRole("button", { name: "Clear All" })) - expect(spies.clearAllFilters).toHaveBeenCalled() - await user.click(screen.getAllByRole("button", { name: "clear filter" })[0]) - expect(spies.onFacetChange).toHaveBeenCalledWith("topic", "Cats", false) - }) + test("Shows filters which are active", async () => { + const activeFacets: Facets = { + topic: ["Cats", "Dogs"], + department: ["1"] + } + const { spies } = setup({ activeFacets }) + const activeDisplay = document.querySelector(".active-search-filters") + if (!(activeDisplay instanceof HTMLElement)) { + throw new Error("Expected activeDisplay to exist") + } + within(activeDisplay).getByText("Cats") + within(activeDisplay).getByText("Dogs") + within(activeDisplay).getByText("1") - test("Clicking a facet checkbox calls onFacetChange", async () => { - const { spies } = setup({ - props: { - facetMap: [ + await user.click(screen.getByRole("button", { name: "Clear All" })) + expect(spies.clearAllFilters).toHaveBeenCalled() + await user.click( + screen.getAllByRole("button", { name: "clear filter" })[0] + ) + expect(spies.onFacetChange).toHaveBeenCalledWith("topic", "Cats", false) + }) + + test("Clicking a facet checkbox calls onFacetChange", async () => { + const { spies } = setup({ + facetManifest: [ { - ...defaultFacetMap[0], + ...defaultFacetManifest[0], expandedOnLoad: true } ] - } + }) + await user.click(screen.getByRole("checkbox", { name: "Cats" })) + expect(spies.onFacetChange).toHaveBeenCalledWith("topic", "Cats", true) }) - await user.click(screen.getByRole("checkbox", { name: "Cats" })) - expect(spies.onFacetChange).toHaveBeenCalledWith("topic", "Cats", true) - }) - test("it accepts a label function to convert codes to names", () => { - setup({ - props: { - facetMap: [ + test("it accepts a label function to convert codes to names", () => { + setup({ + facetManifest: [ { - ...defaultFacetMap[1], + ...defaultFacetManifest[1], labelFunction: getDepartmentName, expandedOnLoad: true } ] - } - }) - screen.getByRole("checkbox", { - name: "Civil and Environmental Engineering" - }) - screen.getByRole("checkbox", { - name: "Mechanical Engineering" + }) + screen.getByRole("checkbox", { + name: "Civil and Environmental Engineering" + }) + screen.getByRole("checkbox", { + name: "Mechanical Engineering" + }) }) - }) - test("Automically includes a zero-count option for active facets with no matches", () => { - const activeFacets: Facets = { - topic: ["Cats"] - } - const { rerender } = setup({ - props: { + test("Automically includes a zero-count option for active facets with no matches", () => { + const activeFacets: Facets = { + topic: ["Cats"] + } + const { rerender } = setup({ activeFacets, - facetMap: [ + facetManifest: [ { - ...defaultFacetMap[0], + ...defaultFacetManifest[0], expandedOnLoad: true } ] - } + }) + const checkboxCount = () => screen.getAllByRole("checkbox").length + expect(checkboxCount()).toBe(3) + screen.getByRole("checkbox", { name: "Cats" }) + screen.getByRole("checkbox", { name: "Dogs" }) + screen.getByRole("checkbox", { name: "Donkeys" }) + rerender({ + activeFacets: { + topic: ["Cats", "Dragons"] + } + }) + expect(checkboxCount()).toBe(4) + screen.getByRole("checkbox", { name: "Dragons" }) }) - const checkboxCount = () => screen.getAllByRole("checkbox").length - expect(checkboxCount()).toBe(3) - screen.getByRole("checkbox", { name: "Cats" }) - screen.getByRole("checkbox", { name: "Dogs" }) - screen.getByRole("checkbox", { name: "Monkeys" }) - rerender({ + + test("Displays filterable facet if type='filterable'", async () => { + setup({ facetManifest: [{ ...defaultFacetManifest[0] }] }) + const textbox = screen.queryByRole("textbox", { name: "Search Topics" }) + expect(!!textbox).toBe(type === "filterable") + if (type !== "filterable") return + expect(screen.getAllByRole("checkbox")).toHaveLength(3) + invariant(textbox, "Expected textbox to exist") + await user.type(textbox, "D") + const box0 = screen.queryByRole("checkbox", { name: "Cats" }) + const box1 = screen.queryByRole("checkbox", { name: "Dogs" }) + const box2 = screen.queryByRole("checkbox", { name: "Donkeys" }) + expect(!!box0).toBe(false) + expect(!!box1).toBe(true) + expect(!!box2).toBe(true) + }) + } +) + +describe("FacetDisplay with type='group' facet", () => { + const multiFacetGroup: MultiFacetGroupOptions = { + type: "group", + facets: [ + { value: true, name: "certification", label: "With Certificate" }, + { value: false, name: "free", label: "Free Stuff" } as const + ] + } + const defaultFacetManifest = [multiFacetGroup] + + const defaultAggregations = { + certification: [ + { key: "false", doc_count: 10 }, + { key: "true", doc_count: 20 } + ], + free: [ + { key: "false", doc_count: 30 }, + { key: "true", doc_count: 40 } + ] + } + + const setup = (props?: Partial) => + setupFacetDisplay({ + facetManifest: defaultFacetManifest, + facetOptions: defaultAggregations, + ...props + }) + + test("Displays correct number of checkboxes with appropriate counts", () => { + setup() + const checkboxes = screen.getAllByRole("checkbox") + const withCerts = screen.getByRole("checkbox", { + name: "With Certificate" + }) + const free = screen.getByRole("checkbox", { name: "Free Stuff" }) + expect(checkboxes).toEqual([withCerts, free]) + + const labels = document.querySelectorAll(".facet-label") + expect(labels[0].textContent).toBe("With Certificate20") + expect(labels[1].textContent).toBe("Free Stuff30") + }) + + test("Clicking a checkbox calls onFacetChange", async () => { + const { spies } = setup() + await user.click(screen.getByRole("checkbox", { name: "With Certificate" })) + expect(spies.onFacetChange).toHaveBeenCalledWith( + "certification", + "true", + true + ) + + await user.click(screen.getByRole("checkbox", { name: "Free Stuff" })) + expect(spies.onFacetChange).toHaveBeenCalledWith("free", "false", true) + }) + + test.each([ + { facetOptions: { free: [] }, rendered: false }, + { + facetOptions: { free: [{ key: "false", doc_count: 10 }] }, + rendered: true + } + ])( + "Does not display the group if no facets available", + ({ facetOptions, rendered }) => { + setup({ + facetOptions + }) + expect(!!document.querySelector(".facets")).toBe(rendered) + } + ) + + test("Displays group entries that are active even if empty", () => { + setup({ + facetOptions: { + certification: [] + }, activeFacets: { - topic: ["Cats", "Dragons"] + certification: true } }) - expect(checkboxCount()).toBe(4) - screen.getByRole("checkbox", { name: "Dragons" }) + const checkbox = screen.getByRole("checkbox", { name: "With Certificate" }) + expect(checkbox).toBeTruthy() }) }) diff --git a/src/facet_display/FacetDisplay.tsx b/src/facet_display/FacetDisplay.tsx index c90d821..543e56c 100644 --- a/src/facet_display/FacetDisplay.tsx +++ b/src/facet_display/FacetDisplay.tsx @@ -1,27 +1,27 @@ -import React, { useCallback } from "react" +import React, { useMemo } from "react" import FilterableFacet from "./FilterableFacet" import Facet from "./Facet" -import SearchFilter from "./SearchFilter" +import FilterIndicator from "./FilterIndicator" import type { FacetManifest, Facets, Aggregation, Bucket, - BooleanFacets + BooleanFacets, + BucketWithLabel } from "./types" import { LEVELS, DEPARTMENTS } from "../constants" - -export type BucketWithLabel = Bucket & { label: string } +import MultiFacetGroup from "./MultiFacetGroup" interface FacetDisplayProps { - facetMap: FacetManifest + facetManifest: FacetManifest /** * Returns the aggregation options for a given group. * * If `activeFacets` includes a facet with no results, that facet will * automatically be included in the facet options. */ - facetOptions: (group: string) => Aggregation | null + facetOptions: Record activeFacets: Facets & BooleanFacets clearAllFilters: () => void onFacetChange: (name: string, value: string, isEnabled: boolean) => void @@ -64,86 +64,144 @@ const resultsWithLabels = ( * results. */ const includeActiveZerosInBuckets = ( - groupKey: string, - buckets: Bucket[], - params: Facets + bucketGroups: Record, + params: Facets | BooleanFacets ) => { - const opts = [...buckets] - const active = params[groupKey as keyof Facets] ?? [] - const actives = Array.isArray(active) ? active : [active] - actives.forEach(key => { - if (!opts.find(o => o.key === key)) { - opts.push({ key: String(key), doc_count: 0 }) + const copy = { ...bucketGroups } + Object.entries(params).forEach(([groupKey, active]) => { + if (!copy[groupKey]) { + // params might include non-facets. + return } + if (active.length === 0) return + const actives = Array.isArray(active) ? active : [active] + const existing = new Set(copy[groupKey].map(bucket => bucket.key)) + copy[groupKey] = [...copy[groupKey]] ?? [] + actives.forEach(key => { + if (!existing.has(key)) { + copy[groupKey].push({ key: String(key), doc_count: 0 }) + } + }) }) - return opts + + return copy } +/** + * Display available facets for search UI. + * + */ const AvailableFacets: React.FC> = ({ - facetMap, + facetManifest, facetOptions, activeFacets, onFacetChange }) => { - const allFacetOptions: FacetDisplayProps["facetOptions"] = useCallback( - name => { - return includeActiveZerosInBuckets( - name, - facetOptions(name) ?? [], - activeFacets - ) - }, + const allOpts = useMemo( + () => includeActiveZerosInBuckets(facetOptions, activeFacets), [facetOptions, activeFacets] ) return ( <> - {facetMap.map(facetSetting => - facetSetting.useFilterableFacet ? ( - - onFacetChange(e.target.name, e.target.value, e.target.checked) - } - expandedOnLoad={facetSetting.expandedOnLoad} - /> - ) : ( - - onFacetChange(e.target.name, e.target.value, e.target.checked) - } - selected={activeFacets[facetSetting.name] || []} - expandedOnLoad={facetSetting.expandedOnLoad} - /> - ) - )} + {facetManifest.map(facetSettings => { + if (!facetSettings.type || facetSettings.type === "static") { + return ( + + onFacetChange(e.target.name, e.target.value, e.target.checked) + } + selected={activeFacets[facetSettings.name] || []} + expandedOnLoad={facetSettings.expandedOnLoad} + /> + ) + } else if (facetSettings.type === "filterable") { + return ( + + onFacetChange(e.target.name, e.target.value, e.target.checked) + } + selected={activeFacets[facetSettings.name] || []} + expandedOnLoad={facetSettings.expandedOnLoad} + /> + ) + } else if (facetSettings.type === "group") { + if (facetSettings.facets.length === 0) return null + const { name, value } = facetSettings.facets[0] + /** + * Assumption: no two FacetManifest entry will have the same name and + * value for first facet in a group. + * (It would be silly to include the same name-value pair twice). + */ + const key = `${name}-${value}` + return ( + + onFacetChange(e.target.name, e.target.value, e.target.checked) + } + activeFacets={activeFacets} + /> + ) + } + console.error("Unrecognized facet configuration.") + return null + })} ) } +type FacetValue = { + name: string + value: string | boolean + label?: string +} + +/** + * Display available facets along with "clear all" button and buttons indicating + * currently active facets. + */ const FacetDisplay = React.memo( function FacetDisplay(props: FacetDisplayProps) { const { - facetMap, + facetManifest, facetOptions, activeFacets, clearAllFilters, onFacetChange } = props + const activeFacetValues = facetManifest.flatMap( + (facetSetting): FacetValue[] => { + if (facetSetting.type === "group") { + return facetSetting.facets.filter( + ({ name, value }) => activeFacets[name] === value + ) + } else { + return (activeFacets[facetSetting.name] ?? []).map(value => ({ + value, + name: facetSetting.name, + label: facetSetting.labelFunction?.(value) + })) + } + } + ) + return ( <>
@@ -157,21 +215,16 @@ const FacetDisplay = React.memo( Clear All
- {facetMap.map(facetSetting => - (activeFacets[facetSetting.name] || []).map((facet, i) => ( - - onFacetChange(facetSetting.name, facet, false) - } - labelFunction={facetSetting.labelFunction || null} - /> - )) - )} + {activeFacetValues.map(({ name, value, label }) => ( + onFacetChange(name, String(value), false)} + /> + ))} { + function setup(props?: Partial) { + const onClickStub = jest.fn() + + render( + + ) + + return { onClickStub } + } + + it("should render a search filter correctly", () => { + const label = "Some Label" + setup({ label }) + screen.getByText(label) + }) + + it("should trigger clearFacet function on click", async () => { + const { onClickStub } = setup() + const button = screen.getByRole("button", { name: "clear filter" }) + await user.click(button) + expect(onClickStub).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/facet_display/FilterIndicator.tsx b/src/facet_display/FilterIndicator.tsx new file mode 100644 index 0000000..0d4f37d --- /dev/null +++ b/src/facet_display/FilterIndicator.tsx @@ -0,0 +1,30 @@ +import React from "react" + +interface FilterIndicatorProps { + label: string + onClick: () => void +} + +/** + * Indicates that a search filter is active. + */ +export default function FilterIndicator(props: FilterIndicatorProps) { + const { onClick, label } = props + + return ( +
+
{label}
+ +
+ ) +} +export type { FilterIndicatorProps } diff --git a/src/facet_display/FilterableFacet.tsx b/src/facet_display/FilterableFacet.tsx index 8125c9e..dc8fb68 100644 --- a/src/facet_display/FilterableFacet.tsx +++ b/src/facet_display/FilterableFacet.tsx @@ -3,7 +3,7 @@ import { contains } from "ramda" import Fuse from "fuse.js" import { SearchFacetItem } from "./SearchFacetItem" -import { BucketWithLabel } from "./FacetDisplay" +import { BucketWithLabel } from "./types" // the `.search method returns records like { item, refindex } // where item is the facet and refIndex is it's index in the original @@ -43,7 +43,7 @@ function FilterableFacet(props: Props) { results }, [filterText, results]) - const facets = (filteredResults || results) ?? [] + const buckets = (filteredResults || results) ?? [] return results && results.length === 0 ? null : (
) diff --git a/src/facet_display/SearchFilter.test.tsx b/src/facet_display/SearchFilter.test.tsx deleted file mode 100644 index 6636f1f..0000000 --- a/src/facet_display/SearchFilter.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react" -import { shallow } from "enzyme" - -import SearchFilter from "./SearchFilter" - -describe("SearchFilter", () => { - function setup() { - const onClickStub = jest.fn() - - const render = (props = {}) => - shallow() - - return { render, onClickStub } - } - - it("should render a search filter correctly", () => { - const value = "Upcoming" - const labelFunc = (input: string) => { - return input.toUpperCase() - } - const { render } = setup() - const wrapper = render({ - value, - labelFunction: labelFunc - }) - const label = wrapper.text() - expect(label.includes(value.toUpperCase())).toBeTruthy() - }) - - it("should trigger clearFacet function on click", async () => { - const { render, onClickStub } = setup() - const wrapper = render({ value: "ocw" }) - wrapper.find(".remove-filter-button").simulate("click") - expect(onClickStub).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/facet_display/SearchFilter.tsx b/src/facet_display/SearchFilter.tsx deleted file mode 100644 index 5706d39..0000000 --- a/src/facet_display/SearchFilter.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react" - -interface Props { - value: string - labelFunction?: ((value: string) => string | null) | null - clearFacet: () => void -} - -export default function SearchFilter(props: Props) { - const { value, clearFacet, labelFunction } = props - - return ( -
-
- {labelFunction ? labelFunction(value) : value} -
- -
- ) -} diff --git a/src/facet_display/types.ts b/src/facet_display/types.ts index 02041a6..2ec9f6c 100644 --- a/src/facet_display/types.ts +++ b/src/facet_display/types.ts @@ -3,6 +3,8 @@ export interface Bucket { doc_count: number } +export type BucketWithLabel = Bucket & { label: string } + export type Aggregation = Bucket[] export type Aggregations = Map @@ -12,15 +14,34 @@ export type GetSearchPageSize = (ui: string | null) => number export type FacetKey = keyof Facets export type BooleanFacetKey = keyof BooleanFacets +/** + * Configure a single facet with multiple values. For example, facet name + * "department" with values "1", "2", ... "21". + */ export type SingleFacetOptions = { + type?: "static" | "filterable" name: FacetKey title: string - useFilterableFacet: boolean expandedOnLoad: boolean labelFunction?: ((value: string) => string) | null } -export type FacetManifest = SingleFacetOptions[] +/** + * Configure a group of facets with multiple names and specific values. For + * example, + * "Certification" with value `true` + * "Professional" with value `false` + */ +export type MultiFacetGroupOptions = { + type: "group" + facets: { + value: boolean | string + name: BooleanFacetKey + label: string + }[] +} + +export type FacetManifest = (SingleFacetOptions | MultiFacetGroupOptions)[] export interface Facets { platform?: string[] diff --git a/yarn.lock b/yarn.lock index fe13c66..55a7e9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -348,7 +348,7 @@ "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" @@ -505,12 +505,12 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/create-cache-key-function@^27.4.2": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz" - integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" "@jest/environment@^29.0.3": version "29.0.3" @@ -597,6 +597,13 @@ dependencies: "@sinclair/typebox" "^0.24.1" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.0.0": version "29.0.0" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz" @@ -647,23 +654,24 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== +"@jest/types@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz" + integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A== dependencies: + "@jest/schemas" "^29.0.0" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" - "@types/yargs" "^16.0.0" + "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz" - integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.0.0" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -704,7 +712,7 @@ "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -834,6 +842,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.41.tgz" integrity sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -848,118 +861,95 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@swc/core-android-arm-eabi@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.3.0.tgz#6ff8c5020e13794b536d6f0fd89817f2f10e8250" - integrity sha512-1F/U0Vh78ZL7OUlCfaRWCtnYnIfsMA8WDtKyf3UT9b3C0L5HajB9TgMH4c0OKhjfP5Q2/M1/Pm00A+96nhKH8A== - dependencies: - "@swc/wasm" "1.2.122" - -"@swc/core-android-arm64@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.3.0.tgz#e09c3f6cc478f2c501d5441eb566a4e2c5b88368" - integrity sha512-dtryoOvQ27s9euAcLinExuaU+mMr8o0N8CBTH3f+JwKjQsIa9v0jPOjJ9jaWktnAdDy/FztB5iBCqTAwbqRG/w== - dependencies: - "@swc/wasm" "1.2.130" - -"@swc/core-darwin-arm64@1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.0.tgz" - integrity sha512-WSf29/wneQf5k7mdLKqaSRLDycIZaLATc6m7BKpFi34iCGSvXJfc375OrVG9BS0rReX5LT49XxXp6GQs9oFmVA== - -"@swc/core-darwin-x64@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.0.tgz#37d89cfa54311db31302fed8888db03460ccd166" - integrity sha512-eDa1EZAnchMtkdZ52bWfseKla370c8BCj/RWAtHJcZMon3WVkWcZlMgZPPiPIxYz8hGtomqs+pkQv34hEVcx0A== - -"@swc/core-freebsd-x64@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.3.0.tgz#652995139abe70d67e082168cbc44e726efdca61" - integrity sha512-ZV9rRmUZqJGCYqnV/3aIJUHELY/MFyABowDN8ijCvN67EjGfoNYx0jpd4hzFWwGC8LohthHNi6hiFfmnvGaKsw== - dependencies: - "@swc/wasm" "1.2.130" - -"@swc/core-linux-arm-gnueabihf@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.0.tgz#25a3260634527a678fa83d5e6aa9726fad6f1f32" - integrity sha512-3fPWh4SB3lz0ZlQWsHjqZFJK1SIkYqjLpm6mR1jzp/LJx4Oq1baid9CP1eiLd/rijSIgVdUJNMGfiOK9uymEbw== - dependencies: - "@swc/wasm" "1.2.130" - -"@swc/core-linux-arm64-gnu@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.0.tgz#f9fd72628f19504a62d663e80fc0a306913c34bd" - integrity sha512-CavXNYHKaPTMOvRXh1u7ZfMS5hKDXNSWTdeo+1+2M2XLCP0r0+2Iaeg0IZJD8nIwAlwwP8+rskan2Ekq6jaIfw== - -"@swc/core-linux-arm64-musl@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.0.tgz#a80dd31211d6d866139cee6237edd919393c5f08" - integrity sha512-/3UiX8jH+OWleJbqYiwJEf4GQKP6xnm/6gyBt7V0GdhM4/ETMvzTFUNRObgpmxYMhXmNGAlxekU8+0QuAvyRJQ== - -"@swc/core-linux-x64-gnu@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.0.tgz#6463fc20af367391da3e612438e8f302b89c6d6b" - integrity sha512-Ds76Lu7vfE01rgFcf9O1OuNBwQSHBpGwGOKGnwob6T2SCR4DBQz4MD0jLw/tdCZGR8x7NVMteBzQAp3CsUORZw== - -"@swc/core-linux-x64-musl@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.0.tgz#534d1b5368d42fcf9901cbc33f144367b0961e45" - integrity sha512-fgGq/SyX6DsTgJIujBbopaEu17f8u+cyTsJBluc5cF7HxspB4wC72sdq4KGgUoEYObVTgFejnEBZkm8hLOCwYA== +"@swc/core-darwin-arm64@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz#2b5cdbd34e4162e50de6147dd1a5cb12d23b08e8" + integrity sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ== + +"@swc/core-darwin-x64@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz#6aa7e3c01ab8e5e41597f8a24ff24c4e50936a46" + integrity sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw== + +"@swc/core-linux-arm-gnueabihf@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz#160108633b9e1d1ad05f815bedc7e9eb5d59fc2a" + integrity sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ== + +"@swc/core-linux-arm64-gnu@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz#cbfa512683c73227ad25552f3b3e722b0e7fbd1d" + integrity sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g== + +"@swc/core-linux-arm64-musl@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz#80239cb58fe57f3c86b44617fe784530ec55ee2b" + integrity sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ== + +"@swc/core-linux-x64-gnu@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz#a699c1632de60b6a63b7fdb7abcb4fef317e57ca" + integrity sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg== + +"@swc/core-linux-x64-musl@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz#8e4c203d6bc41e7f85d7d34d0fdf4ef751fa626c" + integrity sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg== + +"@swc/core-win32-arm64-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz#31e3d42b8c0aa79f0ea1a980c0dd1a999d378ed7" + integrity sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA== + +"@swc/core-win32-ia32-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz#a235285f9f62850aefcf9abb03420f2c54f63638" + integrity sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ== + +"@swc/core-win32-x64-msvc@1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz#f84641393b5223450d00d97bfff877b8b69d7c9b" + integrity sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg== + +"@swc/core@^1.5.7": + version "1.5.7" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.5.7.tgz#e1db7b9887d5f34eb4a3256a738d0c5f1b018c33" + integrity sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "0.1.7" + optionalDependencies: + "@swc/core-darwin-arm64" "1.5.7" + "@swc/core-darwin-x64" "1.5.7" + "@swc/core-linux-arm-gnueabihf" "1.5.7" + "@swc/core-linux-arm64-gnu" "1.5.7" + "@swc/core-linux-arm64-musl" "1.5.7" + "@swc/core-linux-x64-gnu" "1.5.7" + "@swc/core-linux-x64-musl" "1.5.7" + "@swc/core-win32-arm64-msvc" "1.5.7" + "@swc/core-win32-ia32-msvc" "1.5.7" + "@swc/core-win32-x64-msvc" "1.5.7" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@swc/core-win32-arm64-msvc@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.0.tgz#1797b9d2729673fa5715315f671de2a52bbd6e37" - integrity sha512-7B7XggbCmm1oHeNvz5ekWmWmJP/WeGpmGZ10Qca3/zrVm+IRN4ZBT+jpWm+cuuYJh0Llr5UYgTFib3cyOLWkJg== +"@swc/jest@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== dependencies: - "@swc/wasm" "1.2.130" + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" -"@swc/core-win32-ia32-msvc@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.0.tgz#835c3f41ff6e01a53cbcfebafe9784e135847f08" - integrity sha512-vDIu5FjoqB3G7awWCyNsUh5UAzTtJPMEwG75Cwx51fxMPxXrVPHP6XpRovIjQ5wiKL5lGqicckieduJkgBvp7Q== +"@swc/types@0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.7.tgz#ea5d658cf460abff51507ca8d26e2d391bafb15e" + integrity sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ== dependencies: - "@swc/wasm" "1.2.130" - -"@swc/core-win32-x64-msvc@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.0.tgz#7197ac806ab0673268cbf394f9dcf0fcec160aa2" - integrity sha512-ZEgMvq01Ningz6IOD6ixrpsfA83u+B/1TwnYmWuRl9hMml9lnPwdg3o1P0pwbSO1moKlUhSwc8WVYmI0bXF+gA== - -"@swc/core@^1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@swc/core/-/core-1.3.0.tgz" - integrity sha512-0mshAzMvdhL0v3lNMJowzMd8Du0bJf+PUTxhVm4uIb/h8qCDQjFERXj0RGejcDFSL7fJzLI3MzS5WR45KDrrLA== - optionalDependencies: - "@swc/core-android-arm-eabi" "1.3.0" - "@swc/core-android-arm64" "1.3.0" - "@swc/core-darwin-arm64" "1.3.0" - "@swc/core-darwin-x64" "1.3.0" - "@swc/core-freebsd-x64" "1.3.0" - "@swc/core-linux-arm-gnueabihf" "1.3.0" - "@swc/core-linux-arm64-gnu" "1.3.0" - "@swc/core-linux-arm64-musl" "1.3.0" - "@swc/core-linux-x64-gnu" "1.3.0" - "@swc/core-linux-x64-musl" "1.3.0" - "@swc/core-win32-arm64-msvc" "1.3.0" - "@swc/core-win32-ia32-msvc" "1.3.0" - "@swc/core-win32-x64-msvc" "1.3.0" - -"@swc/jest@^0.2.22": - version "0.2.22" - resolved "https://registry.npmjs.org/@swc/jest/-/jest-0.2.22.tgz" - integrity sha512-PIUIk9IdB1oAVfF9zNIfYoMBoEhahrrSvyryFANas7swC1cF0L5HR0f9X4qfet46oyCHCBtNcSpN0XJEOFIKlw== - dependencies: - "@jest/create-cache-key-function" "^27.4.2" - -"@swc/wasm@1.2.122": - version "1.2.122" - resolved "https://registry.yarnpkg.com/@swc/wasm/-/wasm-1.2.122.tgz#87a5e654b26a71b2e84b801f41e45f823b856639" - integrity sha512-sM1VCWQxmNhFtdxME+8UXNyPNhxNu7zdb6ikWpz0YKAQQFRGT5ThZgJrubEpah335SUToNg8pkdDF7ibVCjxbQ== - -"@swc/wasm@1.2.130": - version "1.2.130" - resolved "https://registry.yarnpkg.com/@swc/wasm/-/wasm-1.2.130.tgz#88ac26433335d1f957162a9a92f1450b73c176a0" - integrity sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q== + "@swc/counter" "^0.1.3" "@testing-library/dom@^8.0.0": version "8.20.1" @@ -1003,24 +993,24 @@ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/aria-query@^5.0.1": version "5.0.4" @@ -1253,13 +1243,6 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz" integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== -"@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== - dependencies: - "@types/yargs-parser" "*" - "@types/yargs@^17.0.8": version "17.0.12" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz" @@ -1426,16 +1409,21 @@ acorn-walk@^7.1.1: integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== acorn@^7.1.1: version "7.4.1" resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.4.1, acorn@^8.7.1: +acorn@^8.4.1: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +acorn@^8.7.1: version "8.8.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -1539,7 +1527,7 @@ anymatch@^3.0.3: arg@^4.1.0: version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: @@ -2005,7 +1993,7 @@ core-js@^3.24.1: create-require@^1.1.0: version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-fetch@^3.0.4: @@ -2181,7 +2169,7 @@ diff-sequences@^29.0.0: diff@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dir-glob@^3.0.1: @@ -4046,6 +4034,11 @@ json5@^2.2.1: resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.4" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz#b896535fed5b867650acce5a9bd4135ffc7b3bf9" @@ -4180,7 +4173,7 @@ make-dir@^3.0.0: make-error@^1.1.1: version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== make-plural@^7.0.0: @@ -5356,6 +5349,11 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -5400,10 +5398,10 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== -ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -5544,7 +5542,7 @@ util-deprecate@^1.0.1: v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^9.0.1: @@ -5786,7 +5784,7 @@ yargs@^17.3.1: yn@3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: