Files
supabase/apps/docs/lib/partials.utils.test.ts
Nik Richers d46cc88f09 docs: add agent prompts to all 18 framework quickstarts (#47543)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs enhancement: Agent-ready prompt blocks on all 18 framework
quickstart pages.

## What is the current behavior?

Framework quickstarts do not surface a copyable AI prompt. Readers have
to assemble context themselves when asking an AI coding assistant to
follow the guide.

## What is the new behavior?

- Partials at
`apps/docs/content/_partials/ai/quickstart_prompt_{framework}.mdx`
contain `<AiPrompt prompt={...} />` (Prettier multiline single-quoted JS
string with `\n` escapes).
- Each quickstart includes `<$Partial
path="ai/quickstart_prompt_{framework}.mdx" />`.
- Runtime: `AiPrompt` → `PromptPanel` (Copy AI Prompt, expandable).
- Markdown export: `apps/docs/internals/markdown-schema/AiPrompt.ts`
decodes Prettier single-quoted prompt expressions so exported markdown
includes an **AI Prompt** section without quote leak.
- Shared `$Partial` helpers live in `lib/partials.utils.ts`.
- Closes DOCS-1144.

### Example before/after

| | Production | Preview |
| --- | --- | --- |
| Next.js quickstart |
[production](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)
|
[preview](https://docs-git-nikrichers-docs-1144-add-ai-prompt-blo-5af4d8-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs)
|

**Light**

| Before | After |
| --- | --- |
| ![before
light](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-before-light-d1076045.png)
| ![after
light](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-after-light-74996d6c.png)
|

**Dark**

| Before | After |
| --- | --- |
| ![before
dark](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-before-dark-faa391c3.png)
| ![after
dark](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-after-dark-56d1578a.png)
|

### Test plan

- [x] Preview renders AI Prompt panel with copy
- [x] Spot-check Next.js, Flutter, Expo, Vue
- [x] `test-quickstart-prompts` structural
- [x] Markdown export includes **AI Prompt** without quote leak
- [x] Format CI green after prettier/single-quote decode fix

## Additional context

- Worktree:
`~/GitHub/supabase/supabase-worktrees/nikrichers/docs-1144-add-ai-prompt-blocks-to-all-18-framework-quickstarts`
- Skills: `generate-quickstart-prompts` / `test-quickstart-prompts`;
librarian update https://github.com/supabase/docs-agent-skills/pull/21
- `PromptPanel` replaced the older GlassPanel experiment for the
expandable copy UI

---------

Co-authored-by: Nik Richers <nik@validmind.ai>
Co-authored-by: jeremenichelli <jeremenichelli@users.noreply.github.com>
2026-07-24 22:19:04 +00:00

68 lines
2.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { parsePartialVariables, substitutePartialVars } from './partials.utils'
describe('substitutePartialVars', () => {
it('substitutes provided variables', () => {
const content = 'Here is a partial that takes a {{ .var }}.'
expect(substitutePartialVars(content, { var: 'string replacement' })).toBe(
'Here is a partial that takes a string replacement.'
)
})
it('renders unprovided variables as empty strings', () => {
const content = 'Here is a partial that takes a {{ .var }}.'
expect(substitutePartialVars(content, undefined)).toBe('Here is a partial that takes a .')
})
it('ignores variables that are not referenced in the content', () => {
const content = 'Here is a partial that takes a {{ .var }}.'
expect(substitutePartialVars(content, { var: 'correct', extra: 'unused' })).toBe(
'Here is a partial that takes a correct.'
)
})
it('supports hyphenated variable names', () => {
const content = 'This partial has {{ .my-var }}, {{ .another_var }}, and {{ .myVar123 }}.'
expect(
substitutePartialVars(content, {
'my-var': 'hyphenated value',
another_var: 'underscored value',
myVar123: 'alphanumeric value',
})
).toBe('This partial has hyphenated value, underscored value, and alphanumeric value.')
})
it('handles values containing $ replacement patterns literally', () => {
expect(substitutePartialVars('Hello {{ .name }}', { name: '$& is literal' })).toBe(
'Hello $& is literal'
)
})
it('handles keys containing regex metacharacters', () => {
expect(substitutePartialVars('Hello {{ .a.b }}', { 'a.b': 'world' })).toBe('Hello world')
})
})
describe('parsePartialVariables', () => {
it('returns undefined when variables are omitted', () => {
expect(parsePartialVariables(undefined)).toBeUndefined()
})
it('parses JSON object strings', () => {
expect(parsePartialVariables('{ "product": "Auth" }')).toEqual({
product: 'Auth',
})
})
it('accepts already-parsed objects', () => {
expect(parsePartialVariables({ product: 'Auth' })).toEqual({
product: 'Auth',
})
})
it('throws on non-string values', () => {
expect(() => parsePartialVariables('{ "var": [0, 1, 2] }')).toThrow(/valid JSON/)
})
})