import { render } from "@testing-library/react";
import { JSDOM } from "jsdom";
import React, { MutableRefObject } from "react";
import { c as useMemoCache } from "react/compiler-runtime";
import util from "util";
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { initFbt, toJSON } from "./shared-runtime";
React.c = useMemoCache;
const { window: testWindow } = new JSDOM(undefined);
(globalThis as any).document = testWindow.document;
(globalThis as any).window = testWindow.window;
(globalThis as any).React = React;
(globalThis as any).render = render;
initFbt();
(globalThis as any).placeholderFn = function (..._args: Array<any>) {
throw new Error("Fixture not implemented!");
};
export type EvaluatorResult = {
kind: "ok" | "exception" | "UnexpectedError";
value: string;
logs: Array<string>;
};
const EntrypointSchema = z.strictObject({
fn: z.union([z.function(), z.object({})]),
params: z.array(z.any()),
isComponent: z.optional(z.boolean()),
sequentialRenders: z.optional(z.nullable(z.array(z.any()))).default(null),
});
const ExportSchema = z.object({
FIXTURE_ENTRYPOINT: EntrypointSchema,
});
class WrapperTestComponentWithErrorBoundary extends React.Component<
{ fn: any; params: Array<any> },
{ hasError: boolean; error: any }
> {
propsErrorMap: MutableRefObject<Map<any, any>>;
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
this.propsErrorMap = React.createRef() as MutableRefObject<Map<any, any>>;
this.propsErrorMap.current = new Map();
}
static getDerivedStateFromError(error: any) {
return { hasError: true, error: error };
}
override componentDidUpdate() {
if (this.state.hasError) {
this.setState({ hasError: false, error: null });
}
}
override render() {
if (this.state.hasError) {
this.propsErrorMap.current!.set(
this.props,
`[[ (exception in render) ${this.state.error?.toString()} ]]`
);
}
const cachedError = this.propsErrorMap.current!.get(this.props);
if (cachedError != null) {
return cachedError;
}
return React.createElement(WrapperTestComponent, this.props);
}
}
function WrapperTestComponent(props: { fn: any; params: Array<any> }) {
const result = props.fn(...props.params);
if (typeof result === "object" && result != null && "$$typeof" in result) {
return result;
} else {
return toJSON(result);
}
}
function renderComponentSequentiallyForEachProps(
fn: any,
sequentialRenders: Array<any>
): string {
if (sequentialRenders.length === 0) {
throw new Error(
"Expected at least one set of props when using `sequentialRenders`"
);
}
const initialProps = sequentialRenders[0]!;
const results = [];
const { rerender, container } = render(
React.createElement(WrapperTestComponentWithErrorBoundary, {
fn,
params: [initialProps],
})
);
results.push(container.innerHTML);
for (let i = 1; i < sequentialRenders.length; i++) {
rerender(
React.createElement(WrapperTestComponentWithErrorBoundary, {
fn,
params: [sequentialRenders[i]],
})
);
results.push(container.innerHTML);
}
return results.join("\n");
}
type FixtureEvaluatorResult = Omit<EvaluatorResult, "logs">;
(globalThis as any).evaluateFixtureExport = function (
exports: unknown
): FixtureEvaluatorResult {
const parsedExportResult = ExportSchema.safeParse(exports);
if (!parsedExportResult.success) {
const exportDetail =
typeof exports === "object" && exports != null
? `object ${util.inspect(exports)}`
: `${exports}`;
return {
kind: "UnexpectedError",
value: `${fromZodError(parsedExportResult.error)}\nFound ` + exportDetail,
};
}
const entrypoint = parsedExportResult.data.FIXTURE_ENTRYPOINT;
if (entrypoint.sequentialRenders !== null) {
const result = renderComponentSequentiallyForEachProps(
entrypoint.fn,
entrypoint.sequentialRenders
);
return {
kind: "ok",
value: result ?? "null",
};
} else if (typeof entrypoint.fn === "object") {
const result = render(
React.createElement(entrypoint.fn as any, entrypoint.params[0])
).container.innerHTML;
return {
kind: "ok",
value: result ?? "null",
};
} else {
const result = render(React.createElement(WrapperTestComponent, entrypoint))
.container.innerHTML;
return {
kind: "ok",
value: result ?? "null",
};
}
};
export function doEval(source: string): EvaluatorResult {
"use strict";
const originalConsole = globalThis.console;
const logs: Array<string> = [];
const mockedLog = (...args: Array<any>) => {
logs.push(
`${args.map((arg) => {
if (arg instanceof Error) {
return arg.toString();
} else {
return util.inspect(arg);
}
})}`
);
};
(globalThis.console as any) = {
info: mockedLog,
log: mockedLog,
warn: mockedLog,
error: (...args: Array<any>) => {
if (
typeof args[0] === "string" &&
args[0].includes("ReactDOMTestUtils.act` is deprecated")
) {
return;
}
const stack = new Error().stack?.split("\n", 5) ?? [];
for (const stackFrame of stack) {
if (
(stackFrame.includes("at logCaughtError") &&
stackFrame.includes("react-dom-client.development.js")) ||
(stackFrame.includes("at defaultOnRecoverableError") &&
stackFrame.includes("react-dom-client.development.js"))
) {
return;
}
}
mockedLog(...args);
},
table: mockedLog,
trace: () => {},
};
try {
const evalResult: any = eval(`
(() => {
// Exports should be overwritten by source
let exports = {
FIXTURE_ENTRYPOINT: {
fn: globalThis.placeholderFn,
params: [],
},
};
let reachedInvoke = false;
try {
// run in an iife to avoid naming collisions
(() => {${source}})();
reachedInvoke = true;
if (exports.FIXTURE_ENTRYPOINT?.fn === globalThis.placeholderFn) {
return {
kind: "exception",
value: "Fixture not implemented",
};
}
return evaluateFixtureExport(exports);
} catch (e) {
if (!reachedInvoke) {
return {
kind: "UnexpectedError",
value: e.message,
};
} else {
return {
kind: "exception",
value: e.message,
};
}
}
})()`);
const result = {
...evalResult,
logs,
};
return result;
} catch (e) {
return {
kind: "UnexpectedError",
value:
"Unexpected error during eval, possible syntax error?\n" + e.message,
logs,
};
} finally {
globalThis.console = originalConsole;
}
}