diff --git a/packages/cursorless-engine/src/index.ts b/packages/cursorless-engine/src/index.ts
index e00aaf5fce..f762351b0c 100644
--- a/packages/cursorless-engine/src/index.ts
+++ b/packages/cursorless-engine/src/index.ts
@@ -16,3 +16,4 @@ export * from "./testUtil/plainObjectToTarget";
export * from "./util/getPartialTargetDescriptors";
export * from "./util/getPrimitiveTargets";
export * from "./util/grammarHelpers";
+export * from "./scripts/transformRecordedTests/transformations/upgrade";
\ No newline at end of file
diff --git a/packages/cursorless-org/package.json b/packages/cursorless-org/package.json
index 3438d74658..541efea357 100644
--- a/packages/cursorless-org/package.json
+++ b/packages/cursorless-org/package.json
@@ -13,7 +13,9 @@
"clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist ./build"
},
"dependencies": {
+ "@cursorless/common": "workspace:*",
"@cursorless/cheatsheet": "workspace:*",
+ "@cursorless/test-case-component": "workspace:*",
"@mdx-js/loader": "3.0.1",
"@mdx-js/react": "3.0.1",
"@next/mdx": "14.2.15",
@@ -24,6 +26,7 @@
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
+ "@types/js-yaml": "^4.0.9",
"@types/mdx": "2.0.13",
"@types/node": "20.16.0",
"@types/react": "18.3.11",
diff --git a/packages/cursorless-org/src/pages/allowList.tsx b/packages/cursorless-org/src/pages/allowList.tsx
new file mode 100644
index 0000000000..6d748b79c3
--- /dev/null
+++ b/packages/cursorless-org/src/pages/allowList.tsx
@@ -0,0 +1,19 @@
+export const testSelectedFiles = [
+ "bringArgMadeAfterLook.yml",
+ "chuckBlockAirUntilBatt.yml",
+ "cutFine.yml",
+ "chuckLineFine.yml",
+ "bringAirAndBatAndCapToAfterItemEach.yml",
+ "carveLineHarp.yml",
+ "chuckBlockAir.yml",
+ "chuckBlockAirUntilBatt.yml",
+ "chuckBlockBatt.yml",
+ "chuckBlockBattUntilAir.yml",
+ "chuckFine.yml",
+ "chuckLineFineBetweenRisk.yml",
+ "clearBlockFine.yml",
+ "clearFine.yml",
+ "clearLineFine.yml",
+ // "bringAirToEndOfAir.yml", // Shiki intersect error
+ // "chuckBlockBatt2.yml", // Shiki intersect error
+];
diff --git a/packages/cursorless-org/src/pages/component-sheet.tsx b/packages/cursorless-org/src/pages/component-sheet.tsx
new file mode 100644
index 0000000000..55736598d4
--- /dev/null
+++ b/packages/cursorless-org/src/pages/component-sheet.tsx
@@ -0,0 +1,87 @@
+import * as yaml from "js-yaml";
+import fs from "fs";
+import path from "path";
+import Head from "next/head";
+
+import { loadTestCaseFixture } from "@cursorless/test-case-component";
+import { TestCaseComponentPage } from "@cursorless/test-case-component";
+import type { TestCaseFixture } from "@cursorless/common";
+import { testSelectedFiles } from "./allowList";
+
+import { cheatsheetBodyClasses } from "@cursorless/cheatsheet";
+
+const fixturesDir = path.join("../", "../", "data", "fixtures", "recorded");
+
+async function loadYamlFiles(dir: string, selectedFiles?: string[]) {
+ const directoryPath = path.join(process.cwd(), dir);
+ const files = fs.readdirSync(directoryPath);
+ const data: any[] = [];
+
+ files.forEach((file) => {
+ if (
+ path.extname(file) === ".yml" &&
+ (!selectedFiles || selectedFiles.includes(file))
+ ) {
+ try {
+ const filePath = path.join(directoryPath, file);
+ const fileContents = fs.readFileSync(filePath, "utf8");
+ const yamlData: any = yaml.load(fileContents);
+ yamlData.filename = file;
+ data.push(yamlData);
+ } catch {
+ console.error("File load failure", file);
+ }
+ }
+ });
+
+ return data;
+}
+
+// See https://github.com/vercel/next.js/discussions/12325#discussioncomment-1116108
+export async function getStaticProps() {
+ const itemsDirActions = path.join(fixturesDir, "actions");
+ const itemsDirDecorations = path.join(fixturesDir, "decorations");
+
+ const dataActions = await loadYamlFiles(itemsDirActions, testSelectedFiles);
+ const dataDecorations = await loadYamlFiles(
+ itemsDirDecorations,
+ testSelectedFiles,
+ );
+
+ const data_errors: any[] = [];
+
+ const data = (
+ await Promise.all(
+ [...dataActions, ...dataDecorations].map(async (val) => {
+ try {
+ // const upgraded = upgrade(data);
+ const fixture = await loadTestCaseFixture(val);
+ return { ...fixture, raw: val };
+ } catch (err) {
+ console.error(err);
+ data_errors.push(val);
+ return null;
+ }
+ }),
+ )
+ ).filter((test) => test !== undefined);
+
+ if (data_errors.length > 0) {
+ console.error("data errors:", data_errors);
+ }
+
+ return { props: { data, bodyClasses: cheatsheetBodyClasses } };
+}
+
+export function App({ data }: { data: TestCaseFixture[] }) {
+ return (
+ <>
+
+ Cursorless Test Case Component Page
+
+
+ >
+ );
+}
+
+export default App;
diff --git a/packages/cursorless-org/tsconfig.json b/packages/cursorless-org/tsconfig.json
index 2ce4454b4f..3d03265117 100644
--- a/packages/cursorless-org/tsconfig.json
+++ b/packages/cursorless-org/tsconfig.json
@@ -23,6 +23,12 @@
"references": [
{
"path": "../cheatsheet"
+ },
+ {
+ "path": "../cursorless-engine"
+ },
+ {
+ "path": "../test-case-component"
}
],
"exclude": ["node_modules"]
diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json
index 593bcbdc1a..3cf8ca9ad1 100644
--- a/packages/cursorless-vscode/package.json
+++ b/packages/cursorless-vscode/package.json
@@ -1261,7 +1261,9 @@
"clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist ./build"
},
"devDependencies": {
- "@types/fs-extra": "11.0.4",
+ "@types/chai": "^4.3.14",
+ "@types/fs-extra": "^11.0.4",
+ "@types/glob": "^8.1.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "4.17.12",
"@types/nearley": "2.11.5",
diff --git a/packages/test-case-component/jest.config.ts b/packages/test-case-component/jest.config.ts
new file mode 100644
index 0000000000..f7a6a00f40
--- /dev/null
+++ b/packages/test-case-component/jest.config.ts
@@ -0,0 +1,8 @@
+import type { Config } from "jest";
+
+const config: Config = {
+ preset: "ts-jest",
+ testEnvironment: "jsdom",
+};
+
+export default config;
diff --git a/packages/test-case-component/package.json b/packages/test-case-component/package.json
new file mode 100644
index 0000000000..3d37d70a07
--- /dev/null
+++ b/packages/test-case-component/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@cursorless/test-case-component",
+ "version": "0.0.1",
+ "type": "module",
+ "description": "Component for displaying results of test cases in cursorless-vscode-e2e",
+ "main": "./out/index.js",
+ "scripts": {
+ "build": "my-ts-node src/buildDictionary.ts",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "compile:tsc": "tsc --build",
+ "compile:esbuild": "esbuild ./src/index.ts --sourcemap --format=esm --bundle --packages=external --outfile=./out/index.js",
+ "compile": "pnpm compile:tsc && pnpm compile:esbuild",
+ "watch:tsc": "pnpm compile:tsc --watch",
+ "watch:esbuild": "pnpm compile:esbuild --watch",
+ "watch": "pnpm run --filter @cursorless/test-case-component --parallel '/^watch:.*/'",
+ "clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist ./build"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@cursorless/common": "workspace:*",
+ "@cursorless/node-common": "workspace:*",
+ "escape-goat": "4.0.0",
+ "fs-extra": "11.2.0",
+ "js-yaml": "^4.1.0",
+ "prettier": "3.2.5",
+ "react": "^18.2.0",
+ "tsx": "3.12.7",
+ "yaml": "2.2.1"
+ },
+ "types": "./out/index.d.ts",
+ "exports": {
+ ".": {
+ "cursorless:bundler": "./src/index.ts",
+ "default": "./out/index.js"
+ }
+ },
+ "devDependencies": {
+ "@types/fs-extra": "^11.0.4",
+ "@types/jest": "29.5.12",
+ "@types/react": "18.2.71",
+ "jest": "29.7.0",
+ "jest-environment-jsdom": "29.7.0",
+ "shiki": "^3.2.2",
+ "ts-jest": "29.1.2"
+ }
+}
diff --git a/packages/test-case-component/src/components/TestCaseComponentPage.tsx b/packages/test-case-component/src/components/TestCaseComponentPage.tsx
new file mode 100644
index 0000000000..02ffbc7648
--- /dev/null
+++ b/packages/test-case-component/src/components/TestCaseComponentPage.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+import { ShikiComponent } from "./shikiComponent";
+import "../shiki.css";
+import "../styles.css";
+import type { TestCaseFixture } from "@cursorless/common";
+
+export function TestCaseComponentPage({ data }: { data: TestCaseFixture[] }) {
+ return (
+
+
+ Test Component Sheet{" "}
+
+ See the {/* */}
+ full documentation
+ {/* {" "} */}
+ to learn more.
+
+
+
+ {data.map((item: any) => {
+ return ;
+ })}
+
+ );
+}
diff --git a/packages/test-case-component/src/components/shikiComponent.tsx b/packages/test-case-component/src/components/shikiComponent.tsx
new file mode 100644
index 0000000000..045b8ea734
--- /dev/null
+++ b/packages/test-case-component/src/components/shikiComponent.tsx
@@ -0,0 +1,45 @@
+export function ShikiComponent({ data }: { data: any }) {
+ return (
+
+
+
+ JSON
+
+ {JSON.stringify(data, null, 2)}
+
+
+
+ );
+}
+
+const Before = ({ content }: { content: string }) => {
+ return ;
+};
+
+const During = ({ content }: { content: string }) => {
+ if (content) {
+ console.log("🧄", content);
+
+ return (
+
+ );
+ }
+ return <>>;
+};
+
+const After = ({ content }: { content: string }) => {
+ return (
+
+ );
+};
diff --git a/packages/test-case-component/src/generateHtml.spec.ts b/packages/test-case-component/src/generateHtml.spec.ts
new file mode 100644
index 0000000000..a6bdaa1bea
--- /dev/null
+++ b/packages/test-case-component/src/generateHtml.spec.ts
@@ -0,0 +1,314 @@
+import prettier from "prettier";
+import { generateHtml as unformettedFunc } from "./generateHtml";
+
+async function generateHtml(...args: Parameters) {
+ return prettier.format(await unformettedFunc(...args), {
+ singleAttributePerLine: true,
+ htmlWhitespaceSensitivity: "ignore",
+ parser: "babel",
+ });
+}
+
+// uses mocha for most tests
+// okay to stick with Jest if needed
+// cheatsheet might use Jest?
+
+describe("generateHtml", () => {
+ it("should select whole line", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: " const oneLine = 1;\nconst line2 = 2;",
+ selections: [
+ {
+ type: "line",
+ anchor: { line: 1, character: 0 },
+ active: { line: 1, character: 22 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+
+ const
+
+ oneLine
+
+ =
+
+ 1
+ ;
+
+
+ const
+
+ line2
+
+ =
+
+ 2
+ ;
+
+
+
;
+ "
+ `);
+ });
+ it("should select single token", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: " const oneLine = 1;\nconst line2 = 2;",
+ selections: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 8 },
+ active: { line: 0, character: 15 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+
+ const
+
+
+ oneLine
+
+
+ =
+
+ 1
+ ;
+
+
+ const
+
+ line2
+
+ =
+
+ 2
+ ;
+
+
+
;
+ "
+ `);
+ });
+
+ it("should select multiple tokens", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: "const oneLine = 1;",
+ selections: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 6 },
+ active: { line: 0, character: 17 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+ const
+
+
+ oneLine
+
+ =
+
+ 1
+
+ ;
+
+
+
;
+ "
+ `);
+ });
+
+ it("should select inside tokens", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: 'const oneLine = "line";',
+ selections: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 9 },
+ active: { line: 0, character: 19 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+ const
+
+ one
+
+ Line
+
+ =
+
+
+ "li
+
+
+ ne"
+ ;
+
+
+
;
+ "
+ `);
+ });
+
+ it("should select inside single token", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: "const oneLine = 1;",
+ selections: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 9 },
+ active: { line: 0, character: 11 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+ const
+
+ one
+
+ Li
+
+ ne
+
+ =
+
+ 1
+ ;
+
+
+
;
+ "
+ `);
+ });
+ it("should select superset ranges", async () => {
+ expect(
+ await generateHtml(
+ {
+ documentContents: "const oneLine = 1;",
+ selections: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 9 },
+ active: { line: 0, character: 11 },
+ },
+ ],
+ thatMark: [
+ {
+ type: "selection",
+ anchor: { line: 0, character: 6 },
+ active: { line: 0, character: 13 },
+ },
+ ],
+ },
+
+ "typescript",
+ ),
+ ).toMatchInlineSnapshot(`
+ "
+
+
+ const
+
+
+ one
+
+
+ Li
+
+
+ ne
+
+
+ =
+
+ 1
+ ;
+
+
+
;
+ "
+ `);
+ });
+});
diff --git a/packages/test-case-component/src/generateHtml.ts b/packages/test-case-component/src/generateHtml.ts
new file mode 100644
index 0000000000..c89dcd39d8
--- /dev/null
+++ b/packages/test-case-component/src/generateHtml.ts
@@ -0,0 +1,109 @@
+import { createHighlighter, createCssVariablesTheme } from "shiki";
+import type { BundledLanguage } from "shiki";
+
+import type { TargetPlainObject, TestCaseSnapshot } from "@cursorless/common";
+
+import { createDecorations } from "./helpers";
+
+type Lang = BundledLanguage;
+
+const myTheme = createCssVariablesTheme({
+ name: "css-variables",
+ variablePrefix: "--shiki-",
+ variableDefaults: {},
+ fontStyle: true,
+});
+
+/**
+ * Generates HTML content based on the provided state, language, command, and ide.
+ *
+ * @param {TestCaseSnapshot} state - The state object containing the necessary data for HTML generation.
+ * @param {Lang} lang - The language object specifying the language for the HTML content.
+ * @param {any} [command] - (Optional) The command object specifying the command details.
+ * @param {any} [ide] - (Optional) The ide object specifying the IDE details.
+ * @returns {Promise} A promise that resolves to the generated HTML content.
+ */
+export async function generateHtml({
+ stateName,
+ state,
+ languageId: lang,
+ command,
+ ide,
+ raw
+}: {
+ stateName: string;
+ state: TestCaseSnapshot;
+ languageId: BundledLanguage;
+ command?: any; // Replace `any` with the appropriate type if with
+ ide?: any; // Replace `any` with the appropriate type if known
+ raw: any;
+}) {
+ return new HTMLGenerator({ state, lang, command, ide, raw }).generate();
+}
+
+const highlighter = createHighlighter({
+ themes: [myTheme],
+ langs: ["javascript", "typescript"],
+});
+
+class HTMLGenerator {
+ private state: TestCaseSnapshot;
+ private lang: Lang;
+ private command?: any;
+ private ide?: any;
+ private raw: any;
+
+ constructor({
+ state,
+ lang,
+ command,
+ ide,
+ raw
+ }: {
+ state: TestCaseSnapshot,
+ lang: Lang,
+ command?: any,
+ ide?: any,
+ raw?: any
+ }) {
+ this.state = state;
+ this.lang = lang;
+ this.command = command; // Optional command parameter
+ this.ide = ide; // Optional ide parameter
+ this.raw = raw
+ }
+
+
+ async generate() {
+ const decorations = await this.getDecorations();
+ const options = {
+ theme: "css-variables",
+ lang: this.lang,
+ decorations
+ };
+
+ const marker = await highlighter
+ const codeBody = marker.codeToHtml(this.state.documentContents, options)
+ let clipboard = ""
+ if (this.state.clipboard) {
+ clipboard = `clipboard: ${this.state.clipboard}
`
+ }
+ const output = clipboard !== "" ? codeBody + clipboard : codeBody
+ return output
+ }
+
+ async getDecorations() {
+ const potentialMarks = this.state.marks || {}
+ const lines = this.state.documentContents.split("\n")
+ console.log("💎", this.state.thatMark)
+ const decorations = createDecorations({
+ marks: potentialMarks,
+ ide: this.ide,
+ command: this.command,
+ lines,
+ selections: this.state.selections,
+ thatMark: this.state.thatMark
+ })
+ return decorations
+ }
+}
diff --git a/packages/test-case-component/src/helpers.ts b/packages/test-case-component/src/helpers.ts
new file mode 100644
index 0000000000..a27f05e3c3
--- /dev/null
+++ b/packages/test-case-component/src/helpers.ts
@@ -0,0 +1,275 @@
+import type {
+ // PositionPlainObject,
+ SelectionPlainObject,
+ SerializedMarks,
+ TargetPlainObject,
+} from "@cursorless/common";
+
+import type { DecorationItem } from "shiki"
+
+/**
+ * Splits a string into an array of objects containing the line content
+ * and the cumulative offset from the start of the string.
+ *
+ * @param {string} documentContents - The string to split into lines.
+ * @returns {{ line: string, offset: number }[]} An array of objects with line content and cumulative offset.
+ */
+function splitDocumentWithOffsets(documentContents: string): { line: string; offset: number }[] {
+ const lines = documentContents.split("\n");
+ let cumulativeOffset = 0;
+
+ return lines.map((line) => {
+ const result = { line, offset: cumulativeOffset };
+ cumulativeOffset += line.length + 1; // +1 for the newline character
+ return result;
+ });
+}
+
+/**
+ * Creates decorations based on the split document with offsets and marks.
+ *
+ * @param {Object} options - An object containing optional fields like marks, command, or ide.
+ * @param {SerializedMarks} [options.marks] - An object containing marks with line, start, and end positions.
+ * @param {any} [options.command] - (Optional) The command object specifying the command details.
+ * @param {any} [options.ide] - (Optional) The ide object specifying the IDE details.
+ * @returns {DecorationItem[]} An array of decoration objects.
+ */
+function createDecorations(
+ options: {
+ marks?: SerializedMarks;
+ command?: any;
+ ide?: any;
+ lines?: string[]
+ selections?: SelectionPlainObject[]
+ thatMark?: TargetPlainObject[]
+ } = {} // Default to an empty object
+): DecorationItem[] {
+ const { marks, ide, lines, selections, thatMark /* command */ } = options
+ if (thatMark) {
+ console.log("📅", thatMark)
+ }
+
+ const decorations: DecorationItem[] = [];
+ const markDecorations = getMarkDecorations({ marks, lines })
+ const ideFlashDecorations = getIdeFlashDecorations({ lines, ide })
+ decorations.push(...markDecorations);
+ decorations.push(...ideFlashDecorations);
+ const selectionRanges = getSlections({ selections })
+ decorations.push(...selectionRanges);
+
+ if (thatMark) {
+ const modificationReferences = getThatMarks({ thatMark })
+ decorations.push(...modificationReferences);
+ }
+
+ return decorations
+
+}
+
+/**
+ * Generates Shiki decorations for marks on a specific line.
+ *
+ * @param {Object} params - The parameters for generating decorations.
+ * @param {SerializedMarks} [params.marks] - An object containing serialized marks with start and end positions.
+ * @param {number} params.index - The index of the current line being processed.
+ * @param {{ line: string; offset: number }} params.lineData - The line content and its cumulative offset.
+ * @returns {DecorationItem[]} An array of Shiki decorations for the specified line.
+ *
+ */
+function getMarkDecorations({
+ marks,
+ lines
+}: {
+ marks?: SerializedMarks;
+ lines?: string[]
+}): DecorationItem[] {
+ const decorations: DecorationItem[] = [];
+
+ Object.entries(marks || {}).forEach(([key, { start, end }]) => {
+ const [hatType, letter] = key.split(".") as [keyof typeof classesMap, string];
+ console.log("🔑", key, start, end);
+
+ const markLineStart = start.line
+
+ if (!lines) {
+ console.warn("Lines are undefined. Skipping decoration generation.");
+ return [];
+ }
+ const currentLine = lines[markLineStart]
+
+ const searchStart = start.character;
+ const nextLetterIndex = currentLine.indexOf(letter, searchStart);
+
+ if (nextLetterIndex === -1) {
+ console.warn(
+ `Letter "${letter}" not found after position ${searchStart} in line: "${currentLine}"`
+ );
+ return; // Skip this mark if the letter is not found
+ }
+
+ const decorationItem: DecorationItem = {
+ start,
+ end: { line: start.line, character: nextLetterIndex + 1 },
+ properties: {
+ class: getDecorationClass(hatType), // Replace with the desired class name for marks
+ },
+ alwaysWrap: true,
+ }
+
+ console.log("🔑🔑", decorationItem)
+
+ decorations.push(decorationItem);
+ });
+
+ return decorations;
+}
+
+type LineRange = { type: string; start: number; end: number }
+type PositionRange = { type: string; start: { line: number; character: number }; end: { line: number; character: number } };
+
+type RangeType =
+ | LineRange
+ | PositionRange
+
+
+function getIdeFlashDecorations({
+ ide,
+ lines,
+}: {
+ ide?: {
+ flashes?: { style: keyof typeof classesMap; range: RangeType }[];
+ };
+ lines?: string[];
+} = {}): DecorationItem[] {
+ if (!lines) {
+ console.warn("Lines are undefined. Skipping line decorations.");
+ return [];
+ }
+
+ if (!ide?.flashes || !Array.isArray(ide.flashes)) {
+ console.warn("No flashes found in IDE. Skipping line decorations.");
+ return [];
+ }
+
+ const decorations: DecorationItem[] = [];
+
+ const { flashes } = ide
+
+ flashes.forEach(({ style, range }) => {
+ const { type } = range;
+
+ if (isLineRange(range)) {
+ const { start: lineStart, end: lineEnd } = range
+
+ /* Split a multi-line range into single lines so that shiki doesn't add
+ * multiple classes to the same span causing CSS conflicts
+ */
+ for (let line = lineStart; line <= lineEnd; line++) {
+ const contentLine = lines[line];
+ const startPosition = { line, character: 0 };
+ const endPosition = { line, character: contentLine.length };
+ const decorationItem = {
+ start: startPosition,
+ end: endPosition,
+ properties: {
+ class: `${getDecorationClass(style)} full`,
+ },
+ alwaysWrap: true,
+ };
+ console.log("🔥🔥", decorationItem);
+ decorations.push(decorationItem);
+ }
+
+ } else if (isPositionRange(range)) {
+ const { start: rangeStart, end: rangeEnd } = range;
+ const decorationItem = {
+ start: rangeStart,
+ end: rangeEnd,
+ properties: {
+ class: getDecorationClass(style),
+ },
+ alwaysWrap: true,
+ }
+ console.log("🔥🔥", decorationItem)
+ decorations.push(decorationItem);
+ } else {
+ console.warn(`Unknown range type "${type}". Skipping this flash.`);
+ }
+ });
+
+ return decorations;
+}
+
+function getSlections(
+ {
+ selections,
+ }: {
+ selections?: SelectionPlainObject[];
+ lines?: string[]
+ }): DecorationItem[] {
+ const decorations: DecorationItem[] = [];
+ if (selections === undefined || selections.length === 0) {
+ console.warn("Lines are undefined. Skipping decoration generation.");
+ return []
+ }
+ selections.forEach(({ anchor, active }) => {
+ const decorationItem = {
+ start: anchor,
+ end: active,
+ properties: {
+ class: getDecorationClass("selection"),
+ },
+ alwaysWrap: true,
+ }
+ decorations.push(decorationItem)
+ console.log("🟦", decorationItem)
+ })
+
+ return decorations
+}
+
+function getThatMarks({ thatMark }: { thatMark: TargetPlainObject[] }): DecorationItem[] {
+ console.log("☝️", thatMark)
+ const decorations: DecorationItem[] = [];
+ if (thatMark === undefined) {
+ console.warn("thatMarks are undefined. Skipping decoration generation.");
+ return []
+ }
+
+ return decorations
+}
+
+// Type guard for line range
+function isLineRange(range: RangeType): range is LineRange {
+ return typeof range.start === "number"
+ && typeof range.end === "number"
+ && range.type === "line";
+}
+
+// Type guard for position range
+function isPositionRange(
+ range: RangeType
+): range is PositionRange {
+ return typeof range.start === "object"
+ && typeof range.end === "object"
+ && range.type === "character";
+}
+
+
+const DEFAULT_HAT_CLASS = "hat default";
+const classesMap = {
+ default: DEFAULT_HAT_CLASS,
+ pendingDelete: "decoration pendingDeleteBackground",
+ referenced: "decoration referencedBackground",
+ selection: "selection"
+};
+
+function getDecorationClass(key: keyof typeof classesMap): string {
+ return classesMap[key] || DEFAULT_HAT_CLASS;
+}
+
+
+export {
+ splitDocumentWithOffsets,
+ createDecorations
+}
\ No newline at end of file
diff --git a/packages/test-case-component/src/index.ts b/packages/test-case-component/src/index.ts
new file mode 100644
index 0000000000..a98f0c68a6
--- /dev/null
+++ b/packages/test-case-component/src/index.ts
@@ -0,0 +1,2 @@
+export * from "./components/TestCaseComponentPage";
+export * from "./loadTestCaseFixture";
\ No newline at end of file
diff --git a/packages/test-case-component/src/loadTestCaseFixture.ts b/packages/test-case-component/src/loadTestCaseFixture.ts
new file mode 100644
index 0000000000..4a5e48457a
--- /dev/null
+++ b/packages/test-case-component/src/loadTestCaseFixture.ts
@@ -0,0 +1,114 @@
+import type { TargetPlainObject, TestCaseFixture, TestCaseSnapshot } from "@cursorless/common";
+import { generateHtml } from "./generateHtml";
+import type { BundledLanguage } from "shiki";
+
+async function safeGenerateHtml({
+ stateName,
+ state,
+ languageId,
+ command,
+ ide,
+ thatMarkFinalState
+}: {
+ stateName: string;
+ state: TestCaseSnapshot;
+ languageId: BundledLanguage;
+ command?: any; // Replace `any` with the appropriate type if known
+ ide?: any; // Replace `any` with the appropriate type if known
+ thatMarkFinalState?: TargetPlainObject
+}) {
+ console.log("✨" + stateName + "✨");
+ try {
+ const genObj = { stateName, state, languageId, command, ide }
+ return await generateHtml(genObj);
+ } catch (e) {
+ console.error("error in state", stateName, e);
+ console.error(JSON.stringify(state, null, 2));
+ throw e;
+ }
+}
+
+interface loadFixtureProps extends TestCaseFixture {
+ filename: string;
+ languageId: BundledLanguage;
+ initialState: TestCaseSnapshot;
+ finalState: TestCaseSnapshot;
+}
+
+export async function loadTestCaseFixture(data: loadFixtureProps) {
+ try {
+ const before = await getBefore({
+ stateName: "initialState",
+ state: data.initialState,
+ languageId: data.languageId,
+ });
+
+ const during = await getDuring(data);
+
+ const after = await getAfter({
+ stateName: "finalState",
+ state: data.finalState,
+ languageId: data.languageId,
+ });
+
+ return {
+ language: data.languageId,
+ command: data.command.spokenForm,
+ during,
+ before,
+ after,
+ filename: data.filename,
+ };
+ } catch (e) {
+ console.log("error", e);
+ console.log(JSON.stringify(data, null, 2));
+ throw e;
+ }
+}
+
+type Foo = TestCaseSnapshot & TargetPlainObject;
+
+async function getAfter({
+ stateName,
+ state,
+ languageId,
+}: {
+ stateName: string;
+ state: Foo;
+ languageId: BundledLanguage;
+}) {
+ if (!state) {
+ throw new Error("finalState is undefined");
+ }
+ return await safeGenerateHtml({ stateName, state, languageId });
+}
+
+type DataFixture = Partial
+
+async function getDuring(data: DataFixture) {
+ const { command, ide } = data
+ const stateName = "middleState"
+ const state = data.initialState
+ const languageId = data.languageId as BundledLanguage
+ const genObj: TestCaseSnapshot & { stateName: string } = { stateName, state, languageId, raw: data }
+ if (command) {
+ genObj.command = command
+ }
+ if (ide) {
+ genObj.ide = ide
+ }
+
+ return await generateHtml(genObj);
+}
+
+async function getBefore({
+ stateName,
+ state,
+ languageId,
+}: {
+ stateName: string;
+ state: TestCaseSnapshot;
+ languageId: BundledLanguage;
+}) {
+ return await safeGenerateHtml({ stateName, state, languageId });
+}
\ No newline at end of file
diff --git a/packages/test-case-component/src/shiki.css b/packages/test-case-component/src/shiki.css
new file mode 100644
index 0000000000..3ff520baf7
--- /dev/null
+++ b/packages/test-case-component/src/shiki.css
@@ -0,0 +1,48 @@
+:root {
+ --shiki-foreground: #eeeeee;
+ --shiki-color-text: #eeeeee;
+ --shiki-background: #1f1f1f;
+ --shiki-token-constant: #ff6666;
+ --shiki-token-string: #c2ff6d;
+ --shiki-token-comment: #15bc28;
+ --shiki-token-keyword: #f3ff4c;
+ --shiki-token-parameter: #ff8741;
+ --shiki-token-function: #ff2828;
+ --shiki-token-string-expression: #cc0000;
+ --shiki-token-punctuation: #f958ff;
+ --shiki-token-link: #ee0000;
+
+ /* Only required if using lang: 'ansi' */
+ --shiki-ansi-black: #000000;
+ --shiki-ansi-black-dim: #00000080;
+ --shiki-ansi-red: #bb0000;
+ --shiki-ansi-red-dim: #bb000080;
+ --shiki-ansi-green: #00bb00;
+ --shiki-ansi-green-dim: #00bb0080;
+ --shiki-ansi-yellow: #bbbb00;
+ --shiki-ansi-yellow-dim: #bbbb0080;
+ --shiki-ansi-blue: #0000bb;
+ --shiki-ansi-blue-dim: #0000bb80;
+ --shiki-ansi-magenta: #ff00ff;
+ --shiki-ansi-magenta-dim: #ff00ff80;
+ --shiki-ansi-cyan: #00bbbb;
+ --shiki-ansi-cyan-dim: #00bbbb80;
+ --shiki-ansi-white: #eeeeee;
+ --shiki-ansi-white-dim: #eeeeee80;
+ --shiki-ansi-bright-black: #555555;
+ --shiki-ansi-bright-black-dim: #55555580;
+ --shiki-ansi-bright-red: #ff5555;
+ --shiki-ansi-bright-red-dim: #ff555580;
+ --shiki-ansi-bright-green: #00ff00;
+ --shiki-ansi-bright-green-dim: #00ff0080;
+ --shiki-ansi-bright-yellow: #ffff55;
+ --shiki-ansi-bright-yellow-dim: #ffff5580;
+ --shiki-ansi-bright-blue: #5555ff;
+ --shiki-ansi-bright-blue-dim: #5555ff80;
+ --shiki-ansi-bright-magenta: #ff55ff;
+ --shiki-ansi-bright-magenta-dim: #ff55ff80;
+ --shiki-ansi-bright-cyan: #55ffff;
+ --shiki-ansi-bright-cyan-dim: #55ffff80;
+ --shiki-ansi-bright-white: #ffffff;
+ --shiki-ansi-bright-white-dim: #ffffff80;
+}
diff --git a/packages/test-case-component/src/styles.css b/packages/test-case-component/src/styles.css
new file mode 100644
index 0000000000..eaef0b4343
--- /dev/null
+++ b/packages/test-case-component/src/styles.css
@@ -0,0 +1,177 @@
+:root {
+ --line-height: 2rem;
+ --color-border: #0003;
+}
+
+body {
+ display: grid;
+ height: 100vh;
+ gap: 24px;
+ place-items: flex-start;
+ justify-items: stretch;
+ padding: 42px;
+ font-family: firacode, SFMono-Regular, Consolas, "Liberation Mono", Menlo,
+ monospace;
+ /* background-image: radial-gradient(at 30% 89%, hsla(220,76%,68%,1) 0px, transparent 50%), radial-gradient(at 35% 0%, hsla(242,68%,61%,1) 0px, transparent 50%), radial-gradient(at 93% 46%, hsla(129,87%,73%,1) 0px, transparent 50%), radial-gradient(at 23% 49%, hsla(50,77%,67%,1) 0px, transparent 50%), radial-gradient(at 17% 27%, hsla(331,64%,60%,1) 0px, transparent 50%), radial-gradient(at 79% 30%, hsla(151,61%,77%,1) 0px, transparent 50%), radial-gradient(at 26% 40%, hsla(36,65%,63%,1) 0px, transparent 50%); */
+}
+
+pre code {
+ display: grid; /* Ensure each line is treated as a block */
+}
+
+pre {
+ counter-reset: line; /* Initialize a counter for line numbers */
+}
+
+.line::before {
+ counter-increment: line; /* Increment the line counter */
+ content: counter(line); /* Display the current line number */
+ padding-inline: 1em;
+ text-align: right;
+ color: gray; /* Style the line numbers */
+ font-size: 0.9em;
+ position: absolute;
+ left: -40px; /* Align line numbers to the left */
+}
+
+.line {
+ margin-left: 1.5rem;
+ position: relative;
+ width: 100%;
+ display: inline-block;
+ /* display: block; */
+ /* padding-top: 1rem; */
+}
+
+pre.shiki,
+pre.shiki span {
+ line-height: var(--line-height);
+ min-height: var(--line-height);
+ font-size: 20px;
+}
+
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 16px;
+ border: 1px solid var(--color-border);
+ box-shadow: 0px 0px 6px #000a;
+ border-radius: 8px;
+ background: #ccc9;
+ & > div {
+ background: #fff;
+ border: 1px solid var(--color-border);
+ border-radius: 8px;
+ }
+}
+
+pre.shiki {
+ padding: 12px 16px;
+ display: flex;
+ flex-direction: column;
+ .line {
+ display: inline-block;
+ }
+}
+
+.command {
+ /* border-top: 1px solid var(--color-border); */
+ padding: 8px;
+ font-weight: bold;
+ text-align: center;
+}
+
+@keyframes blink {
+ 0%,
+ 60% {
+ border-color: #667f;
+ }
+ 61%,
+ 100% {
+ border-color: #6670;
+ }
+}
+
+.selection {
+ /* display: inline-block; */
+ border-right: 2px solid #00b;
+ animation: blink 1400ms infinite;
+ padding: 0px 0.5px 0px 0;
+ margin: 0 0px 0 -1px;
+ height: var(--line-height);
+}
+
+.selection:not(:empty) {
+ background: #55f2;
+ padding-left: 0.5px;
+}
+.decoration {
+ display: inline-block;
+ /* padding: 0px 0.5px 0px 0.5px; */
+ /* margin: 0 0px 0 -1px; */
+ /* height: var(--line-height); */
+ /* width: 100%; */
+}
+
+.full {
+ width: 100%;
+}
+
+.decoration.pendingDeleteBackground {
+ background-color: #ff00008a;
+}
+
+.decoration.referencedBackground {
+ background-color: #00a2ff4d;
+}
+
+.decoration.justAddedBackground {
+ background-color: #09ff005b;
+}
+
+.decoration.pendingModification0Background {
+ background-color: #8c00ff86;
+}
+
+.decoration.pendingModification1Background {
+ background-color: #ff009d7e;
+}
+
+.decoration.highlight0Background {
+ background-color: #d449ff42;
+}
+
+.decoration.highlight1Background {
+ background-color: #60daff7a;
+}
+
+.decoration.timingCalibrationBackground {
+ background-color: #230026;
+}
+
+.hat {
+ position: relative;
+ display: inline-block;
+ &::after {
+ content: "";
+ /* display: inline; */
+ /* top: 0px; */
+ /* left: 50%; */
+ position: absolute;
+ transform: translate(-110%, -50%);
+ background-color: #b8b6cd;
+ mask-repeat: no-repeat;
+ width: 0.5rem;
+ height: 0.5rem;
+ }
+}
+.hat.default::after {
+ mask-image: url('data:image/svg+xml;utf8,');
+ top: 0.2rem;
+ width: 0.5rem;
+ height: 0.5rem;
+}
+.hat.wing::after {
+ mask-image: url('data:image/svg+xml;utf8,');
+}
diff --git a/packages/test-case-component/src/types.ts b/packages/test-case-component/src/types.ts
new file mode 100644
index 0000000000..16a03c7680
--- /dev/null
+++ b/packages/test-case-component/src/types.ts
@@ -0,0 +1,213 @@
+export type Lang =
+ | "abap"
+ | "actionscript-3"
+ | "ada"
+ | "apache"
+ | "apex"
+ | "apl"
+ | "applescript"
+ | "ara"
+ | "asm"
+ | "astro"
+ | "awk"
+ | "ballerina"
+ | "bat"
+ | "batch"
+ | "beancount"
+ | "berry"
+ | "be"
+ | "bibtex"
+ | "bicep"
+ | "blade"
+ | "c"
+ | "cadence"
+ | "cdc"
+ | "clarity"
+ | "clojure"
+ | "clj"
+ | "cmake"
+ | "cobol"
+ | "codeql"
+ | "ql"
+ | "coffee"
+ | "cpp"
+ | "crystal"
+ | "csharp"
+ | "c#"
+ | "cs"
+ | "css"
+ | "cue"
+ | "cypher"
+ | "cql"
+ | "d"
+ | "dart"
+ | "dax"
+ | "diff"
+ | "docker"
+ | "dockerfile"
+ | "dream-maker"
+ | "elixir"
+ | "elm"
+ | "erb"
+ | "erlang"
+ | "erl"
+ | "fish"
+ | "fsharp"
+ | "f#"
+ | "fs"
+ | "gdresource"
+ | "gdscript"
+ | "gdshader"
+ | "gherkin"
+ | "git-commit"
+ | "git-rebase"
+ | "glimmer-js"
+ | "gjs"
+ | "glimmer-ts"
+ | "gts"
+ | "glsl"
+ | "gnuplot"
+ | "go"
+ | "graphql"
+ | "groovy"
+ | "hack"
+ | "haml"
+ | "handlebars"
+ | "hbs"
+ | "haskell"
+ | "hs"
+ | "hcl"
+ | "hjson"
+ | "hlsl"
+ | "html"
+ | "http"
+ | "imba"
+ | "ini"
+ | "properties"
+ | "java"
+ | "javascript"
+ | "js"
+ | "jinja-html"
+ | "jison"
+ | "json"
+ | "json5"
+ | "jsonc"
+ | "jsonl"
+ | "jsonnet"
+ | "jssm"
+ | "fsl"
+ | "jsx"
+ | "julia"
+ | "kotlin"
+ | "kusto"
+ | "kql"
+ | "latex"
+ | "less"
+ | "liquid"
+ | "lisp"
+ | "logo"
+ | "lua"
+ | "make"
+ | "makefile"
+ | "markdown"
+ | "md"
+ | "marko"
+ | "matlab"
+ | "mdx"
+ | "mermaid"
+ | "narrat"
+ | "nar"
+ | "nextflow"
+ | "nf"
+ | "nginx"
+ | "nim"
+ | "nix"
+ | "objective-c"
+ | "objc"
+ | "objective-cpp"
+ | "ocaml"
+ | "pascal"
+ | "perl"
+ | "php"
+ | "plsql"
+ | "postcss"
+ | "powerquery"
+ | "powershell"
+ | "ps"
+ | "ps1"
+ | "prisma"
+ | "prolog"
+ | "proto"
+ | "pug"
+ | "jade"
+ | "puppet"
+ | "purescript"
+ | "python"
+ | "py"
+ | "r"
+ | "raku"
+ | "perl6"
+ | "razor"
+ | "reg"
+ | "rel"
+ | "riscv"
+ | "rst"
+ | "ruby"
+ | "rb"
+ | "rust"
+ | "rs"
+ | "sas"
+ | "sass"
+ | "scala"
+ | "scheme"
+ | "scss"
+ | "shaderlab"
+ | "shader"
+ | "shellscript"
+ | "bash"
+ | "console"
+ | "sh"
+ | "shell"
+ | "zsh"
+ | "smalltalk"
+ | "solidity"
+ | "sparql"
+ | "sql"
+ | "ssh-config"
+ | "stata"
+ | "stylus"
+ | "styl"
+ | "svelte"
+ | "swift"
+ | "system-verilog"
+ | "tasl"
+ | "tcl"
+ | "tex"
+ | "toml"
+ | "tsx"
+ | "turtle"
+ | "twig"
+ | "typescript"
+ | "ts"
+ | "v"
+ | "vb"
+ | "cmd"
+ | "verilog"
+ | "vhdl"
+ | "viml"
+ | "vim"
+ | "vimscript"
+ | "vue-html"
+ | "vue"
+ | "vyper"
+ | "vy"
+ | "wasm"
+ | "wenyan"
+ | "文言"
+ | "wgsl"
+ | "wolfram"
+ | "xml"
+ | "xsl"
+ | "yaml"
+ | "yml"
+ | "zenscript";
diff --git a/packages/test-case-component/tsconfig.json b/packages/test-case-component/tsconfig.json
new file mode 100644
index 0000000000..ba7a4452d7
--- /dev/null
+++ b/packages/test-case-component/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "lib": ["es5", "es6", "dom"]
+ },
+ "references": [
+ {
+ "path": "../common"
+ },
+ {
+ "path": "../node-common"
+ }
+ ],
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.json",
+ "src/**/*.tsx",
+ "../../typings/**/*.d.ts"
+ ]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b2a5d2a1aa..80f826fc52 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -457,6 +457,12 @@ importers:
'@cursorless/cheatsheet':
specifier: workspace:*
version: link:../cheatsheet
+ '@cursorless/common':
+ specifier: workspace:*
+ version: link:../common
+ '@cursorless/test-case-component':
+ specifier: workspace:*
+ version: link:../test-case-component
'@mdx-js/loader':
specifier: 3.0.1
version: 3.0.1(webpack@5.95.0(esbuild@0.25.0))
@@ -482,6 +488,9 @@ importers:
'@svgr/webpack':
specifier: 8.1.0
version: 8.1.0(typescript@5.6.3)
+ '@types/js-yaml':
+ specifier: ^4.0.9
+ version: 4.0.9
'@types/mdx':
specifier: 2.0.13
version: 2.0.13
@@ -659,9 +668,15 @@ importers:
specifier: ^3.0.8
version: 3.0.8
devDependencies:
+ '@types/chai':
+ specifier: ^4.3.14
+ version: 4.3.20
'@types/fs-extra':
- specifier: 11.0.4
+ specifier: ^11.0.4
version: 11.0.4
+ '@types/glob':
+ specifier: ^8.1.0
+ version: 8.1.0
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
@@ -892,6 +907,58 @@ importers:
specifier: ^10.7.3
version: 10.7.3
+ packages/test-case-component:
+ dependencies:
+ '@cursorless/common':
+ specifier: workspace:*
+ version: link:../common
+ '@cursorless/node-common':
+ specifier: workspace:*
+ version: link:../node-common
+ escape-goat:
+ specifier: 4.0.0
+ version: 4.0.0
+ fs-extra:
+ specifier: 11.2.0
+ version: 11.2.0
+ js-yaml:
+ specifier: ^4.1.0
+ version: 4.1.0
+ prettier:
+ specifier: 3.2.5
+ version: 3.2.5
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ tsx:
+ specifier: 3.12.7
+ version: 3.12.7
+ yaml:
+ specifier: 2.2.1
+ version: 2.2.1
+ devDependencies:
+ '@types/fs-extra':
+ specifier: ^11.0.4
+ version: 11.0.4
+ '@types/jest':
+ specifier: 29.5.12
+ version: 29.5.12
+ '@types/react':
+ specifier: 18.2.71
+ version: 18.2.71
+ jest:
+ specifier: 29.7.0
+ version: 29.7.0(@types/node@20.16.0)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.6.3))
+ jest-environment-jsdom:
+ specifier: 29.7.0
+ version: 29.7.0
+ shiki:
+ specifier: ^3.2.2
+ version: 3.2.2
+ ts-jest:
+ specifier: 29.1.2
+ version: 29.1.2(@babel/core@7.25.8)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.16.0)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.6.3)))(typescript@5.6.3)
+
packages/test-case-recorder:
dependencies:
'@cursorless/common':
@@ -1918,102 +1985,210 @@ packages:
html-webpack-plugin: '>=5'
webpack: '>=5'
+ '@esbuild-kit/cjs-loader@2.4.4':
+ resolution: {integrity: sha512-NfsJX4PdzhwSkfJukczyUiZGc7zNNWZcEAyqeISpDnn0PTfzMJR1aR8xAIPskBejIxBJbIgCCMzbaYa9SXepIg==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
+ '@esbuild-kit/core-utils@3.3.2':
+ resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
+ deprecated: 'Merged into tsx: https://tsx.is'
+
'@esbuild/aix-ppc64@0.25.0':
resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.18.20':
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.25.0':
resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.18.20':
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.25.0':
resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.18.20':
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.25.0':
resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.18.20':
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.25.0':
resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.18.20':
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.25.0':
resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.18.20':
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.25.0':
resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.18.20':
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.25.0':
resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.18.20':
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.25.0':
resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.18.20':
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.25.0':
resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.18.20':
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.25.0':
resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.18.20':
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.25.0':
resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.18.20':
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.25.0':
resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.18.20':
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.25.0':
resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.18.20':
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.25.0':
resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.18.20':
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.25.0':
resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.18.20':
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.25.0':
resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
engines: {node: '>=18'}
@@ -2026,6 +2201,12 @@ packages:
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.18.20':
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.25.0':
resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
engines: {node: '>=18'}
@@ -2038,30 +2219,60 @@ packages:
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.18.20':
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.25.0':
resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+ '@esbuild/sunos-x64@0.18.20':
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.25.0':
resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
+ '@esbuild/win32-arm64@0.18.20':
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-arm64@0.25.0':
resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-ia32@0.18.20':
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-ia32@0.25.0':
resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.18.20':
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.25.0':
resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
engines: {node: '>=18'}
@@ -2813,6 +3024,27 @@ packages:
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
+ '@shikijs/core@3.2.2':
+ resolution: {integrity: sha512-yvlSKVMLjddAGBa2Yu+vUZxuu3sClOWW1AG+UtJkvejYuGM5BVL35s6Ijiwb75O9QdEx6IkMxinHZSi8ZyrBaA==}
+
+ '@shikijs/engine-javascript@3.2.2':
+ resolution: {integrity: sha512-tlDKfhWpF4jKLUyVAnmL+ggIC+0VyteNsUpBzh1iwWLZu4i+PelIRr0TNur6pRRo5UZIv3ss/PLMuwahg9S2hg==}
+
+ '@shikijs/engine-oniguruma@3.2.2':
+ resolution: {integrity: sha512-vyXRnWVCSvokwbaUD/8uPn6Gqsf5Hv7XwcW4AgiU4Z2qwy19sdr6VGzMdheKKN58tJOOe5MIKiNb901bgcUXYQ==}
+
+ '@shikijs/langs@3.2.2':
+ resolution: {integrity: sha512-NY0Urg2dV9ETt3JIOWoMPuoDNwte3geLZ4M1nrPHbkDS8dWMpKcEwlqiEIGqtwZNmt5gKyWpR26ln2Bg2ecPgw==}
+
+ '@shikijs/themes@3.2.2':
+ resolution: {integrity: sha512-Zuq4lgAxVKkb0FFdhHSdDkALuRpsj1so1JdihjKNQfgM78EHxV2JhO10qPsMrm01FkE3mDRTdF68wfmsqjt6HA==}
+
+ '@shikijs/types@3.2.2':
+ resolution: {integrity: sha512-a5TiHk7EH5Lso8sHcLHbVNNhWKP0Wi3yVnXnu73g86n3WoDgEra7n3KszyeCGuyoagspQ2fzvy4cpSc8pKhb0A==}
+
+ '@shikijs/vscode-textmate@10.0.2':
+ resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+
'@sideway/address@4.1.5':
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
@@ -3037,6 +3269,9 @@ packages:
'@types/bonjour@3.5.13':
resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==}
+ '@types/chai@4.3.20':
+ resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==}
+
'@types/chai@5.0.0':
resolution: {integrity: sha512-+DwhEHAaFPPdJ2ral3kNHFQXnTfscEEFsUxzD+d7nlcLrFK23JtNjH71RGasTcHb88b4vVi4mTyfpf8u2L8bdA==}
@@ -3109,6 +3344,9 @@ packages:
'@types/istanbul-reports@3.0.4':
resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+ '@types/jest@29.5.12':
+ resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==}
+
'@types/jest@29.5.13':
resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==}
@@ -3214,6 +3452,9 @@ packages:
'@types/react-router@5.1.20':
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
+ '@types/react@18.2.71':
+ resolution: {integrity: sha512-PxEsB9OjmQeYGffoWnYAd/r5FiJuUw2niFQHPc2v2idwh8wGPkkYzOHuinNJJY6NZqfoTCiOIizDOz38gYNsyw==}
+
'@types/react@18.3.11':
resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==}
@@ -3226,6 +3467,9 @@ packages:
'@types/sax@1.2.7':
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
+ '@types/scheduler@0.26.0':
+ resolution: {integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==}
+
'@types/semver@7.5.8':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@@ -4834,6 +5078,9 @@ packages:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
+ emoji-regex-xs@1.0.0:
+ resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
+
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
@@ -4940,6 +5187,11 @@ packages:
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
engines: {node: '>= 0.4'}
+ esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
esbuild@0.25.0:
resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
engines: {node: '>=18'}
@@ -5696,6 +5948,9 @@ packages:
hast-util-to-estree@3.1.0:
resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==}
+ hast-util-to-html@9.0.5:
+ resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
+
hast-util-to-jsx-runtime@2.3.2:
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==}
@@ -7521,6 +7776,12 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
+ oniguruma-parser@0.11.2:
+ resolution: {integrity: sha512-F7Ld4oDZJCI5/wCZ8AOffQbqjSzIRpKH7I/iuSs1SkhZeCj0wS6PMZ4W6VA16TWHrAo0Y9bBKEJOe7tvwcTXnw==}
+
+ oniguruma-to-es@4.2.0:
+ resolution: {integrity: sha512-MDPs6KSOLS0tKQ7joqg44dRIRZUyotfTy0r+7oEEs6VwWWP0+E2PPDYWMFN0aqOjRyWHBYq7RfKw9GQk2S2z5g==}
+
open@10.1.0:
resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
engines: {node: '>=18'}
@@ -8150,6 +8411,11 @@ packages:
prettier-plugin-svelte:
optional: true
+ prettier@3.2.5:
+ resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+ engines: {node: '>=14'}
+ hasBin: true
+
prettier@3.3.3:
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
@@ -8240,6 +8506,9 @@ packages:
property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+ property-information@7.0.0:
+ resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
+
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -8516,6 +8785,15 @@ packages:
regenerator-transform@0.15.2:
resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
+ regex-recursion@6.0.2:
+ resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
+
+ regex-utilities@2.3.0:
+ resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
+
+ regex@6.0.1:
+ resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
+
regexp-tree@0.1.27:
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
hasBin: true
@@ -8858,6 +9136,9 @@ packages:
engines: {node: '>=4'}
hasBin: true
+ shiki@3.2.2:
+ resolution: {integrity: sha512-0qWBkM2t/0NXPRcVgtLhtHv6Ak3Q5yI4K/ggMqcgLRKm4+pCs3namgZlhlat/7u2CuqNtlShNs9lENOG6n7UaQ==}
+
side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
@@ -9429,6 +9710,27 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ ts-jest@29.1.2:
+ resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==}
+ engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@babel/core': '>=7.0.0-beta.0 <8'
+ '@jest/types': ^29.0.0
+ babel-jest: ^29.0.0
+ esbuild: '*'
+ jest: ^29.0.0
+ typescript: '>=4.3 <6'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ '@jest/types':
+ optional: true
+ babel-jest:
+ optional: true
+ esbuild:
+ optional: true
+
ts-jest@29.2.5:
resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
@@ -9486,6 +9788,10 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@3.12.7:
+ resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==}
+ hasBin: true
+
tuf-js@1.1.7:
resolution: {integrity: sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -10054,6 +10360,10 @@ packages:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
+ yaml@2.2.1:
+ resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
+ engines: {node: '>= 14'}
+
yaml@2.6.0:
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
engines: {node: '>= 14'}
@@ -11780,78 +12090,159 @@ snapshots:
source-map-url: 0.4.1
webpack: 5.95.0(esbuild@0.25.0)(webpack-cli@5.1.4)
+ '@esbuild-kit/cjs-loader@2.4.4':
+ dependencies:
+ '@esbuild-kit/core-utils': 3.3.2
+ get-tsconfig: 4.8.1
+
+ '@esbuild-kit/core-utils@3.3.2':
+ dependencies:
+ esbuild: 0.18.20
+ source-map-support: 0.5.21
+
+ '@esbuild-kit/esm-loader@2.6.5':
+ dependencies:
+ '@esbuild-kit/core-utils': 3.3.2
+ get-tsconfig: 4.8.1
+
'@esbuild/aix-ppc64@0.25.0':
optional: true
+ '@esbuild/android-arm64@0.18.20':
+ optional: true
+
'@esbuild/android-arm64@0.25.0':
optional: true
+ '@esbuild/android-arm@0.18.20':
+ optional: true
+
'@esbuild/android-arm@0.25.0':
optional: true
+ '@esbuild/android-x64@0.18.20':
+ optional: true
+
'@esbuild/android-x64@0.25.0':
optional: true
+ '@esbuild/darwin-arm64@0.18.20':
+ optional: true
+
'@esbuild/darwin-arm64@0.25.0':
optional: true
+ '@esbuild/darwin-x64@0.18.20':
+ optional: true
+
'@esbuild/darwin-x64@0.25.0':
optional: true
+ '@esbuild/freebsd-arm64@0.18.20':
+ optional: true
+
'@esbuild/freebsd-arm64@0.25.0':
optional: true
+ '@esbuild/freebsd-x64@0.18.20':
+ optional: true
+
'@esbuild/freebsd-x64@0.25.0':
optional: true
+ '@esbuild/linux-arm64@0.18.20':
+ optional: true
+
'@esbuild/linux-arm64@0.25.0':
optional: true
+ '@esbuild/linux-arm@0.18.20':
+ optional: true
+
'@esbuild/linux-arm@0.25.0':
optional: true
+ '@esbuild/linux-ia32@0.18.20':
+ optional: true
+
'@esbuild/linux-ia32@0.25.0':
optional: true
+ '@esbuild/linux-loong64@0.18.20':
+ optional: true
+
'@esbuild/linux-loong64@0.25.0':
optional: true
+ '@esbuild/linux-mips64el@0.18.20':
+ optional: true
+
'@esbuild/linux-mips64el@0.25.0':
optional: true
+ '@esbuild/linux-ppc64@0.18.20':
+ optional: true
+
'@esbuild/linux-ppc64@0.25.0':
optional: true
+ '@esbuild/linux-riscv64@0.18.20':
+ optional: true
+
'@esbuild/linux-riscv64@0.25.0':
optional: true
+ '@esbuild/linux-s390x@0.18.20':
+ optional: true
+
'@esbuild/linux-s390x@0.25.0':
optional: true
+ '@esbuild/linux-x64@0.18.20':
+ optional: true
+
'@esbuild/linux-x64@0.25.0':
optional: true
'@esbuild/netbsd-arm64@0.25.0':
optional: true
+ '@esbuild/netbsd-x64@0.18.20':
+ optional: true
+
'@esbuild/netbsd-x64@0.25.0':
optional: true
'@esbuild/openbsd-arm64@0.25.0':
optional: true
+ '@esbuild/openbsd-x64@0.18.20':
+ optional: true
+
'@esbuild/openbsd-x64@0.25.0':
optional: true
+ '@esbuild/sunos-x64@0.18.20':
+ optional: true
+
'@esbuild/sunos-x64@0.25.0':
optional: true
+ '@esbuild/win32-arm64@0.18.20':
+ optional: true
+
'@esbuild/win32-arm64@0.25.0':
optional: true
+ '@esbuild/win32-ia32@0.18.20':
+ optional: true
+
'@esbuild/win32-ia32@0.25.0':
optional: true
+ '@esbuild/win32-x64@0.18.20':
+ optional: true
+
'@esbuild/win32-x64@0.25.0':
optional: true
@@ -12985,6 +13376,39 @@ snapshots:
'@rushstack/eslint-patch@1.10.4': {}
+ '@shikijs/core@3.2.2':
+ dependencies:
+ '@shikijs/types': 3.2.2
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
+ '@shikijs/engine-javascript@3.2.2':
+ dependencies:
+ '@shikijs/types': 3.2.2
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.2.0
+
+ '@shikijs/engine-oniguruma@3.2.2':
+ dependencies:
+ '@shikijs/types': 3.2.2
+ '@shikijs/vscode-textmate': 10.0.2
+
+ '@shikijs/langs@3.2.2':
+ dependencies:
+ '@shikijs/types': 3.2.2
+
+ '@shikijs/themes@3.2.2':
+ dependencies:
+ '@shikijs/types': 3.2.2
+
+ '@shikijs/types@3.2.2':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ '@shikijs/vscode-textmate@10.0.2': {}
+
'@sideway/address@4.1.5':
dependencies:
'@hapi/hoek': 9.3.0
@@ -13232,6 +13656,8 @@ snapshots:
dependencies:
'@types/node': 20.16.0
+ '@types/chai@4.3.20': {}
+
'@types/chai@5.0.0': {}
'@types/connect-history-api-fallback@1.5.4':
@@ -13322,6 +13748,11 @@ snapshots:
dependencies:
'@types/istanbul-lib-report': 3.0.3
+ '@types/jest@29.5.12':
+ dependencies:
+ expect: 29.7.0
+ pretty-format: 29.7.0
+
'@types/jest@29.5.13':
dependencies:
expect: 29.7.0
@@ -13424,6 +13855,12 @@ snapshots:
'@types/history': 4.7.11
'@types/react': 18.3.11
+ '@types/react@18.2.71':
+ dependencies:
+ '@types/prop-types': 15.7.13
+ '@types/scheduler': 0.26.0
+ csstype: 3.1.3
+
'@types/react@18.3.11':
dependencies:
'@types/prop-types': 15.7.13
@@ -13437,6 +13874,8 @@ snapshots:
dependencies:
'@types/node': 20.16.0
+ '@types/scheduler@0.26.0': {}
+
'@types/semver@7.5.8': {}
'@types/send@0.17.4':
@@ -15239,6 +15678,8 @@ snapshots:
emittery@0.13.1: {}
+ emoji-regex-xs@1.0.0: {}
+
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {}
@@ -15398,6 +15839,31 @@ snapshots:
is-date-object: 1.0.5
is-symbol: 1.0.4
+ esbuild@0.18.20:
+ optionalDependencies:
+ '@esbuild/android-arm': 0.18.20
+ '@esbuild/android-arm64': 0.18.20
+ '@esbuild/android-x64': 0.18.20
+ '@esbuild/darwin-arm64': 0.18.20
+ '@esbuild/darwin-x64': 0.18.20
+ '@esbuild/freebsd-arm64': 0.18.20
+ '@esbuild/freebsd-x64': 0.18.20
+ '@esbuild/linux-arm': 0.18.20
+ '@esbuild/linux-arm64': 0.18.20
+ '@esbuild/linux-ia32': 0.18.20
+ '@esbuild/linux-loong64': 0.18.20
+ '@esbuild/linux-mips64el': 0.18.20
+ '@esbuild/linux-ppc64': 0.18.20
+ '@esbuild/linux-riscv64': 0.18.20
+ '@esbuild/linux-s390x': 0.18.20
+ '@esbuild/linux-x64': 0.18.20
+ '@esbuild/netbsd-x64': 0.18.20
+ '@esbuild/openbsd-x64': 0.18.20
+ '@esbuild/sunos-x64': 0.18.20
+ '@esbuild/win32-arm64': 0.18.20
+ '@esbuild/win32-ia32': 0.18.20
+ '@esbuild/win32-x64': 0.18.20
+
esbuild@0.25.0:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.0
@@ -16423,6 +16889,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ hast-util-to-html@9.0.5:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.0
+ property-information: 7.0.0
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.4
+ zwitch: 2.0.4
+
hast-util-to-jsx-runtime@2.3.2:
dependencies:
'@types/estree': 1.0.7
@@ -18914,6 +19394,15 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
+ oniguruma-parser@0.11.2: {}
+
+ oniguruma-to-es@4.2.0:
+ dependencies:
+ emoji-regex-xs: 1.0.0
+ oniguruma-parser: 0.11.2
+ regex: 6.0.1
+ regex-recursion: 6.0.2
+
open@10.1.0:
dependencies:
default-browser: 5.2.1
@@ -19523,6 +20012,8 @@ snapshots:
dependencies:
prettier: 3.3.3
+ prettier@3.2.5: {}
+
prettier@3.3.3: {}
pretty-bytes@5.6.0: {}
@@ -19598,6 +20089,8 @@ snapshots:
property-information@6.5.0: {}
+ property-information@7.0.0: {}
+
proto-list@1.2.4: {}
proxy-addr@2.0.7:
@@ -19949,6 +20442,16 @@ snapshots:
dependencies:
'@babel/runtime': 7.25.7
+ regex-recursion@6.0.2:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ regex-utilities@2.3.0: {}
+
+ regex@6.0.1:
+ dependencies:
+ regex-utilities: 2.3.0
+
regexp-tree@0.1.27: {}
regexp.prototype.flags@1.5.3:
@@ -20354,6 +20857,17 @@ snapshots:
interpret: 1.4.0
rechoir: 0.6.2
+ shiki@3.2.2:
+ dependencies:
+ '@shikijs/core': 3.2.2
+ '@shikijs/engine-javascript': 3.2.2
+ '@shikijs/engine-oniguruma': 3.2.2
+ '@shikijs/langs': 3.2.2
+ '@shikijs/themes': 3.2.2
+ '@shikijs/types': 3.2.2
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
side-channel@1.0.6:
dependencies:
call-bind: 1.0.7
@@ -20994,6 +21508,24 @@ snapshots:
ts-interface-checker@0.1.13: {}
+ ts-jest@29.1.2(@babel/core@7.25.8)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.16.0)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.6.3)))(typescript@5.6.3):
+ dependencies:
+ bs-logger: 0.2.6
+ fast-json-stable-stringify: 2.1.0
+ jest: 29.7.0(@types/node@20.16.0)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.6.3))
+ jest-util: 29.7.0
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
+ semver: 7.6.3
+ typescript: 5.6.3
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ '@babel/core': 7.25.8
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.25.8)
+ esbuild: 0.25.0
+
ts-jest@29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.16.0)(ts-node@10.9.2(@types/node@20.16.0)(typescript@5.6.3)))(typescript@5.6.3):
dependencies:
bs-logger: 0.2.6
@@ -21055,6 +21587,14 @@ snapshots:
tslib@2.8.1: {}
+ tsx@3.12.7:
+ dependencies:
+ '@esbuild-kit/cjs-loader': 2.4.4
+ '@esbuild-kit/core-utils': 3.3.2
+ '@esbuild-kit/esm-loader': 2.6.5
+ optionalDependencies:
+ fsevents: 2.3.3
+
tuf-js@1.1.7:
dependencies:
'@tufjs/models': 1.0.4
@@ -21785,6 +22325,8 @@ snapshots:
yaml@1.10.2: {}
+ yaml@2.2.1: {}
+
yaml@2.6.0: {}
yargs-parser@20.2.9: {}
diff --git a/tsconfig.json b/tsconfig.json
index 20ea84c510..1bdd00ddfb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -65,6 +65,9 @@
{
"path": "./packages/sentence-parser"
},
+ {
+ "path": "./packages/test-case-component"
+ },
{
"path": "./packages/test-case-recorder"
},