Files
supabase/apps/www/components/Partners/PartnerIntakeForm.sync.test.ts
51a167d910 feat(www): partners landing page (#47874)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added a redesigned Partners page with partnership options, benefits,
application steps, FAQs, integration resources, and featured partners.
- Added an on-page partner application form with questions tailored to
the selected partnership type.
- Added a confirmation message with next steps and a link to the OAuth
integration guide.
- **Bug Fixes**
- Updated partner application links to open the form on the Partners
page.
  - Added support for checkbox-group fields in forms.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alan Daniel <stylesshjs@gmail.com>
Co-authored-by: Alex Hall <alex.hall@supabase.io>
Co-authored-by: Dion Zeneli <101271736+Dionysos288@users.noreply.github.com>
2026-09-25 15:53:32 +02:00

170 lines
6.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import hubspotSnapshotJson from './__generated__/partner-intake-form.hubspot.json'
import { fields } from './PartnerIntakeForm.fields'
import { staticFormCrmRegistry } from '@/lib/staticFormCrm'
// staticFormCrm.ts is `import 'server-only'` gated; that guard throws when
// bundled for a client, but under plain Node/Vitest (no Next.js webpack
// aliasing) it throws unconditionally, so it must be stubbed for this test.
vi.mock('server-only', () => ({}))
/**
* Cross-checks PartnerIntakeForm.tsx + staticFormCrm.ts against the
* generated HubSpot schema snapshot (apps/www/scripts/syncPartnerIntakeForm.mjs).
*
* This is the actual "can't silently drift" guarantee: re-run
* `pnpm run sync:partner-form` to refresh the snapshot from the live
* HubSpot form, and this test starts failing the moment the snapshot no
* longer matches the hand-authored form — forcing a deliberate update to
* PartnerIntakeForm.tsx / staticFormCrm.ts instead of a silent HubSpot
* submission failure.
*/
interface HubSpotFieldSnapshot {
name: string
label: string
fieldType: string
required: boolean
options: { label: string; value: string }[]
description?: string
dependsOn?: { field: string; values: string[] }
}
const hubspotSnapshot = hubspotSnapshotJson as {
portalId: number
formGuid: string
fields: HubSpotFieldSnapshot[]
}
const crmConfig = staticFormCrmRegistry['partners/become-a-partner']?.hubspot
if (!crmConfig) {
throw new Error(
"Expected a hubspot CRM config for 'partners/become-a-partner' in staticFormCrm.ts"
)
}
const fieldMap = crmConfig.fieldMap ?? {}
const excludeFields = new Set(crmConfig.excludeFields ?? [])
const hubspotFieldsByName = new Map(hubspotSnapshot.fields.map((f) => [f.name, f]))
// Company-object fields are bound via a `0-2/` prefix (see HubSpotClient) and
// aren't part of the Contact-field schema this endpoint returns — treat them
// as known rather than flagging them as unmapped.
const KNOWN_NON_CONTACT_FIELDS = new Set(['name', 'website'])
/** The HubSpot property name a given app field forwards to. */
function hubspotNameFor(appFieldName: string): string {
const mapped = fieldMap[appFieldName] ?? appFieldName
const slashIndex = mapped.indexOf('/')
return slashIndex === -1 ? mapped : mapped.slice(slashIndex + 1)
}
describe('PartnerIntakeForm HubSpot sync', () => {
it('maps every required, non-excluded HubSpot field to an app field', () => {
const forwardedHubspotNames = new Set(
fields.filter((f) => !excludeFields.has(f.name)).map((f) => hubspotNameFor(f.name))
)
const missing = hubspotSnapshot.fields
.filter((f) => f.required)
.map((f) => f.name)
.filter((name) => !forwardedHubspotNames.has(name))
expect(missing).toEqual([])
})
it('binds company name and website to the Company object', () => {
// The `0-2/` prefix makes HubSpotClient submit these against the Company
// object rather than the Contact — dropping it silently mis-files them.
expect(fieldMap.company_name).toBe('0-2/name')
expect(fieldMap.company_website).toBe('0-2/website')
})
it('keeps requiredness and conditional visibility in sync for every mapped field', () => {
// A field HubSpot requires but we don't (or one we only show for the wrong
// partner types) makes the HubSpot submission fail for some users only.
const mismatches: string[] = []
for (const field of fields) {
if (excludeFields.has(field.name)) continue
const hubspotName = hubspotNameFor(field.name)
const hubspotField = hubspotFieldsByName.get(hubspotName)
if (!hubspotField) continue
if (Boolean(field.required) !== hubspotField.required) {
mismatches.push(
`${field.name}: required is ${Boolean(field.required)}, HubSpot's ${hubspotName} is ${hubspotField.required}`
)
}
const ours = field.showWhen
? {
field: hubspotNameFor(field.showWhen.field),
values: [
...(field.showWhen.in ?? []),
...(field.showWhen.equals !== undefined ? [field.showWhen.equals] : []),
].sort(),
}
: undefined
const theirs = hubspotField.dependsOn
? { field: hubspotField.dependsOn.field, values: [...hubspotField.dependsOn.values].sort() }
: undefined
if (JSON.stringify(ours) !== JSON.stringify(theirs)) {
mismatches.push(
`${field.name}: shown when ${JSON.stringify(ours ?? 'always')}, HubSpot's ${hubspotName} when ${JSON.stringify(theirs ?? 'always')}`
)
}
}
expect(mismatches).toEqual([])
})
it('forwards every non-excluded app field to a HubSpot property that actually exists', () => {
// An app field forwarded to a HubSpot property HubSpot doesn't define
// causes the *entire* HubSpot submission to fail (see HubSpotClient).
const unknown = fields
.filter((f) => !excludeFields.has(f.name))
.map((f) => hubspotNameFor(f.name))
.filter((name) => !KNOWN_NON_CONTACT_FIELDS.has(name))
.filter((name) => !hubspotFieldsByName.has(name))
expect(unknown).toEqual([])
})
it('keeps option values in exact sync for every mapped select/checkbox-group field', () => {
// Checked in BOTH directions: a value we send that HubSpot doesn't
// recognize, and — the bug that slipped through when this only checked
// one direction — a live HubSpot option we're not offering at all (e.g.
// a "Marketplace Partner" option nobody could ever select).
const mismatches: string[] = []
for (const field of fields) {
if (field.type !== 'select' && field.type !== 'checkbox-group') continue
if (excludeFields.has(field.name)) continue
const hubspotName = hubspotNameFor(field.name)
const hubspotField = hubspotFieldsByName.get(hubspotName)
if (!hubspotField || hubspotField.options.length === 0) continue
const ourValues = new Set(field.options.map((o) => o.value))
const hubspotValues = new Set(hubspotField.options.map((o) => o.value))
for (const value of ourValues) {
if (!hubspotValues.has(value)) {
mismatches.push(`${field.name}: "${value}" is not a valid ${hubspotName} option`)
}
}
for (const value of hubspotValues) {
if (!ourValues.has(value)) {
mismatches.push(`${field.name}: missing HubSpot's "${value}" option for ${hubspotName}`)
}
}
}
expect(mismatches).toEqual([])
})
})