From b4c0f0260e16e8c947437ed0a104cd70ccdeecff Mon Sep 17 00:00:00 2001 From: Flame119052 <146615010+Flame119052@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:46:07 +0000 Subject: [PATCH] fix: spread getConstructorParams tuples in download templates Generic page.tsx and test.ts downloads passed the whole getConstructorParams() result as one argument, so a tuple like [{ limit: 7 }, 3] reconstructed as [[{ limit: 7 }, 3]]. Spread arrays and wrap legacy plain-object returns as a single argument. --- lib/format-solver-constructor-call.ts | 11 +++++ lib/react/DownloadDropdown.tsx | 8 +-- tests/format-solver-constructor-call.test.ts | 52 ++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 lib/format-solver-constructor-call.ts create mode 100644 tests/format-solver-constructor-call.test.ts diff --git a/lib/format-solver-constructor-call.ts b/lib/format-solver-constructor-call.ts new file mode 100644 index 0000000..542d02f --- /dev/null +++ b/lib/format-solver-constructor-call.ts @@ -0,0 +1,11 @@ +/** + * `getConstructorParams()` returns either a tuple of constructor arguments or + * a legacy single-object value. Download templates must unpack tuples. + */ +export function getConstructorArgumentList(params: unknown): unknown[] { + return Array.isArray(params) ? params : [params] +} + +export function formatNewSolverExpression(solverName: string): string { + return `new ${solverName}(...(Array.isArray(input) ? input : [input]) as any)` +} diff --git a/lib/react/DownloadDropdown.tsx b/lib/react/DownloadDropdown.tsx index e5d0a6a..b595a4f 100644 --- a/lib/react/DownloadDropdown.tsx +++ b/lib/react/DownloadDropdown.tsx @@ -1,5 +1,6 @@ -import { useState, useRef, useEffect } from "react" +import { useEffect, useRef, useState } from "react" import type { BaseSolver } from "../BaseSolver" +import { formatNewSolverExpression } from "../format-solver-constructor-call" interface DownloadDropdownProps { solver: BaseSolver @@ -102,7 +103,8 @@ export const inputProblem = ${JSON.stringify(params, null, 2)} export default () => { const solver = useMemo(() => { - return new ${solverName}(inputProblem as any) + const input = inputProblem + return ${formatNewSolverExpression(solverName)} }, []) return } @@ -137,7 +139,7 @@ import { test, expect } from "bun:test" test("${solverName} should solve problem correctly", () => { const input = ${JSON.stringify(params, null, 2)} - const solver = new ${solverName}(input as any) + const solver = ${formatNewSolverExpression(solverName)} solver.solve() expect(solver).toMatchSolverSnapshot(import.meta.path) diff --git a/tests/format-solver-constructor-call.test.ts b/tests/format-solver-constructor-call.test.ts new file mode 100644 index 0000000..26fbfb6 --- /dev/null +++ b/tests/format-solver-constructor-call.test.ts @@ -0,0 +1,52 @@ +import { expect, test } from "bun:test" +import { + formatNewSolverExpression, + getConstructorArgumentList, +} from "../lib/format-solver-constructor-call" + +test("unpacks constructor-param tuples instead of nesting them", () => { + expect(getConstructorArgumentList([])).toEqual([]) + expect(getConstructorArgumentList([{ limit: 7 }])).toEqual([{ limit: 7 }]) + expect(getConstructorArgumentList([{ limit: 7 }, 3])).toEqual([ + { limit: 7 }, + 3, + ]) + expect(getConstructorArgumentList([[1, 2, 3]])).toEqual([[1, 2, 3]]) +}) + +test("wraps a legacy plain-object return as a single argument", () => { + expect(getConstructorArgumentList({ limit: 7 })).toEqual([{ limit: 7 }]) +}) + +test("generated construction spreads tuples into the constructor", () => { + class ProbeSolver { + input: { limit: number } + scale: number | undefined + constructor(input: { limit: number }, scale?: number) { + this.input = input + this.scale = scale + } + } + + const cases: Array<{ params: unknown; expected: [unknown, unknown?] }> = [ + { params: [], expected: [undefined, undefined] }, + { params: [{ limit: 7 }], expected: [{ limit: 7 }, undefined] }, + { params: [{ limit: 7 }, 3], expected: [{ limit: 7 }, 3] }, + { params: [[1, 2, 3]], expected: [[1, 2, 3], undefined] }, + { params: { limit: 7 }, expected: [{ limit: 7 }, undefined] }, + ] + + expect(formatNewSolverExpression("ProbeSolver")).toBe( + "new ProbeSolver(...(Array.isArray(input) ? input : [input]) as any)", + ) + + for (const { params, expected } of cases) { + const solver = new ProbeSolver( + ...(getConstructorArgumentList(params) as [ + { limit: number }, + number | undefined, + ]), + ) + expect([solver.input, solver.scale] as unknown[]).toEqual(expected) + } +})