{
  "version": 3,
  "sources": ["../../src/app/ai/agents/evals2/scenarios/forms/contactForm.eval.ts"],
  "sourcesContent": ["import { assert, isExternalModuleIdentifier } from \"@framerjs/shared\"\nimport {\n\tisCodeComponentNode,\n\tisFormBooleanInputNode,\n\tisFormPlainTextInputNode,\n\tisFormSelectNode,\n} from \"document/models/CanvasTree/nodes/utils/nodeCheck.ts\"\nimport { withFormVariants } from \"document/models/CanvasTree/traits/WithFormVariants.ts\"\nimport type { WithFormContainer } from \"document/models/CanvasTree/traits/forms/WithFormContainer.ts\"\nimport { isFormConfigVerifiedEmailDestination } from \"document/models/CanvasTree/traits/forms/formConfig.ts\"\nimport { isString } from \"utils/typeChecks.ts\"\nimport { IMPLEMENTATION_GUIDE_FROM_INDEX_QUERY } from \"../../../tools/readProjectQueryTypes.ts\"\nimport { agentEvalAsset } from \"../../harness/asset.ts\"\nimport { createEvalExportZipFixture } from \"../../harness/fixture.ts\"\nimport { getDescendantText } from \"../helpers.ts\"\n\nconst contactFormRequestId = \"XL9YHuumf\"\n\nfunction findByInputName<T extends { formInputName?: unknown }>(inputs: readonly T[], pattern: RegExp): T | undefined {\n\treturn inputs.find(input => typeof input.formInputName === \"string\" && pattern.test(input.formInputName))\n}\n\nevaluation(\n\t\"Forms: Contact Form\",\n\tcreateEvalExportZipFixture(\"contact-form\", agentEvalAsset(\"./contactForm.fixture.zip\"), {\n\t\truntimeTarget: \"browser\",\n\t}),\n\t{\n\t\tid: \"contact-form\",\n\t\truntimeTarget: \"browser\",\n\t\trequestId: contactFormRequestId,\n\t\tstepIndex: 0,\n\t\tmaxSteps: 10,\n\t\tstopWhenPassed: true,\n\t},\n\tasync ({ engine, report, steps, tools }) => {\n\t\ttools.reportReplayChecks(report, { requestId: contactFormRequestId, stepIndex: 0 })\n\n\t\tconst formContainers = engine.tree.query().wherePropertyEquals<WithFormContainer>(\"isFormContainer\", true).asArray()\n\t\tassert(\n\t\t\tformContainers.length === 1,\n\t\t\t`Expected exactly one form container on the page, found ${formContainers.length}.`,\n\t\t)\n\t\tconst [form] = formContainers\n\t\tassert(form, \"Expected a form container to be created.\")\n\n\t\tconst formNodes = Array.from(form.walk())\n\t\tconst textInputs = formNodes.filter(isFormPlainTextInputNode)\n\t\tconst booleanInputs = formNodes.filter(isFormBooleanInputNode)\n\t\tconst selectInputs = formNodes.filter(isFormSelectNode)\n\n\t\tlet configuredEmails: readonly string[] = []\n\t\tif (form.formSaveId) {\n\t\t\tawait engine.stores.formsStore.requestLatestFormConfig(form.id, form.formSaveId)\n\t\t\tconst config = engine.stores.formsStore.getLocalFormConfig(form.id, form.formSaveId)\n\t\t\tconst emailDestination = config?.destinations.find(isFormConfigVerifiedEmailDestination)\n\t\t\tconfiguredEmails = emailDestination?.emails?.map(entry => entry.email) ?? []\n\t\t}\n\n\t\treport.correctness.scored(\"requests the Forms implementation guide before changing the project\", () => {\n\t\t\tconst guidePosition = steps.firstReadProjectQueryPosition(\n\t\t\t\tquery => query.type === IMPLEMENTATION_GUIDE_FROM_INDEX_QUERY && query.name === \"Forms\",\n\t\t\t)\n\t\t\tsteps.expectBefore(guidePosition, steps.firstProjectMutationPosition())\n\t\t})\n\n\t\treport.correctness.required(\"creates a name input\", () => {\n\t\t\tassert(findByInputName(textInputs, /name/iu), \"Expected a Name text input.\")\n\t\t})\n\n\t\treport.correctness.required(\"creates an email input with type=email\", () => {\n\t\t\tconst emailInput = findByInputName(textInputs, /email/iu)\n\t\t\tassert(emailInput, \"Expected an Email text input.\")\n\t\t\texpect(emailInput.formTextInputType).toBe(\"email\")\n\t\t})\n\n\t\treport.correctness.scored(\"creates a url input for the existing website\", () => {\n\t\t\tconst websiteInput = findByInputName(textInputs, /website|url/iu)\n\t\t\tassert(websiteInput, \"Expected a Website text input.\")\n\t\t\texpect(websiteInput.formTextInputType).toBe(\"url\")\n\t\t})\n\n\t\treport.correctness.required(\"creates a budget select with multiple options\", () => {\n\t\t\tconst budgetSelect = findByInputName(selectInputs, /budget/iu)\n\t\t\tassert(budgetSelect, \"Expected a Budget select input.\")\n\t\t\tconst options = (budgetSelect.formSelectOptions ?? []).filter(option => option.type === \"option\")\n\t\t\texpect(options.length).toBeGreaterThan(1)\n\t\t})\n\n\t\treport.correctness.required(\"creates a project type radio group with app / web / both grouped by same name\", () => {\n\t\t\tconst radios = booleanInputs.filter(input => input.formBooleanInputType === \"radio\")\n\t\t\texpect(radios.length).toBeGreaterThanOrEqual(3)\n\t\t\texpect(new Set(radios.map(radio => radio.formInputName)).size).toBe(1)\n\t\t})\n\n\t\treport.correctness.required(\"creates a privacy policy checkbox\", () => {\n\t\t\tconst privacy = findByInputName(booleanInputs, /privacy/iu)\n\t\t\tassert(privacy, \"Expected a Privacy Policy checkbox.\")\n\t\t\texpect(privacy.formBooleanInputType).toBe(\"checkbox\")\n\t\t})\n\n\t\treport.correctness.required(\"marks name and email as required\", () => {\n\t\t\texpect(findByInputName(textInputs, /name/iu)?.formInputRequired).toBe(true)\n\t\t\texpect(findByInputName(textInputs, /email/iu)?.formInputRequired).toBe(true)\n\t\t})\n\n\t\t// Not asked for, but logical\n\t\treport.correctness.required(\"marks the privacy policy as required\", () => {\n\t\t\texpect(findByInputName(booleanInputs, /privacy/iu)?.formInputRequired).toBe(true)\n\t\t})\n\n\t\treport.accuracy.scored(\"visually marks required fields with '*'\", () => {\n\t\t\tfor (const pattern of [/name/iu, /email/iu]) {\n\t\t\t\tconst input = findByInputName(textInputs, pattern)\n\t\t\t\tassert(input, `Expected required field matching ${pattern}.`)\n\t\t\t\t// Walk up to the wrapping label frame and gather rich text under it.\n\t\t\t\tlet label = \"\"\n\t\t\t\tfor (const ancestor of input.ancestors()) {\n\t\t\t\t\t// The wrapping element of the input is a FrameNode with htmlType: \"label\",\n\t\t\t\t\t// so if we hit the form already, we know we've gone too far.\n\t\t\t\t\tif (ancestor.id === form.id) break\n\t\t\t\t\tconst text = getDescendantText(ancestor)\n\t\t\t\t\tif (text.length > 0) {\n\t\t\t\t\t\tlabel = text\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\texpect(label).toMatch(/\\*/u)\n\t\t\t}\n\t\t})\n\n\t\treport.correctness.scored(\"configures an email submission destination\", () => {\n\t\t\tif (configuredEmails.length > 0) return\n\t\t\tthrow new Error(\"Form has no email submission destination configured.\")\n\t\t})\n\n\t\treport.accuracy.scored(\"sends submissions to the email shown on the page\", () => {\n\t\t\t// The contact page already has an email address. Ideally the agent would reuse that one.\n\t\t\tconst pageContactEmail = \"hello@snaildesign.co\"\n\t\t\tif (configuredEmails.some(email => email.toLowerCase() === pageContactEmail)) return\n\t\t\tthrow new Error(\n\t\t\t\t`Expected destination to include \"${pageContactEmail}\", got: [${configuredEmails.join(\", \") || \"\"}]`,\n\t\t\t)\n\t\t})\n\n\t\treport.correctness.scored(\"submit button has success and error form variants enabled\", () => {\n\t\t\tconst submitId = form.formSubmitButtonId\n\t\t\tassert(submitId, \"Expected the form to reference a formSubmitButtonId.\")\n\t\t\tconst button = engine.tree.get(submitId)\n\t\t\tassert(button && withFormVariants(button), \"Expected submit button with form variants.\")\n\t\t\texpect(button.formButtonSuccessVariantEnabled).toBe(true)\n\t\t\texpect(button.formButtonErrorVariantEnabled).toBe(true)\n\t\t})\n\n\t\t// An error/success state with only colours is poor a11y and UX. A good output\n\t\t// will include a change in the copy as well.\n\t\treport.accuracy.scored(\"communicates success via copy change or redirect (not colour alone)\", () => {\n\t\t\tif (form.formOnSuccessRedirectUrl !== undefined) return\n\t\t\tconst button = engine.tree.get(form.formSubmitButtonId)\n\t\t\tassert(button && withFormVariants(button), \"Expected submit button with form variants.\")\n\t\t\tconst successVariant = engine.tree.get(button.formButtonSuccessVariant)\n\t\t\tassert(successVariant, \"Expected a success variant on the submit button.\")\n\t\t\tconst defaultLabel = getDescendantText(button).trim()\n\t\t\tconst successLabel = getDescendantText(successVariant).trim()\n\t\t\texpect(successLabel.length).toBeGreaterThan(0)\n\t\t\texpect(successLabel).not.toBe(defaultLabel)\n\t\t})\n\n\t\t// Leaving inputs populated after a successful submit is confusing \u2014 the form\n\t\t// should either clear or navigate away.\n\t\treport.accuracy.scored(\"clears inputs or redirects after a successful submission\", () => {\n\t\t\tif (form.formOnSuccessRedirectUrl !== undefined) return\n\t\t\texpect((form.onSuccess ?? []).length).toBeGreaterThan(0)\n\t\t})\n\n\t\treport.correctness.required(\"removes the phone block from the page\", () => {\n\t\t\texpect(engine.tree.get(\"e7A7RMIz4\")).toBe(null)\n\t\t})\n\n\t\t// Use native form fields. The prompt mentions nothing about Formspark, so the\n\t\t// model should not try to do that.\n\t\treport.correctness.forbidden(\"does not use remote (non-local) form components\", () => {\n\t\t\tconst remoteComponents: string[] = []\n\t\t\tfor (const node of formNodes) {\n\t\t\t\tif (!isCodeComponentNode(node)) continue\n\t\t\t\tif (!isExternalModuleIdentifier(node.codeComponentIdentifier)) continue\n\t\t\t\tremoteComponents.push(isString(node.name) ? node.name : \"Unnamed component\")\n\t\t\t}\n\t\t\texpect(remoteComponents).toEqual([])\n\t\t})\n\t},\n)\n"],
  "mappings": "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",
  "names": []
}
