-
Notifications
You must be signed in to change notification settings - Fork 4
test(cli-e2e): add comprehensive init E2E tests #894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
57e626c
c24a7e0
2ecab1f
03b481e
153d3c9
1077660
07b1aa5
ca3666f
9e05782
88655da
dafbdeb
d22d607
f85d2d3
3fbdb84
2ee6c3e
d7fff6e
ec4a60c
74e1d1a
4065fe4
43e869e
e4402d8
fe4e07c
ebe3c86
2af6920
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| { | ||
| "permissions": { | ||
| "deny": ["Read(**/.env)"] | ||
| }, | ||
| "hooks": { | ||
| "PostToolUse": [ | ||
| { | ||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| SANITY_E2E_TOKEN= | ||
| SANITY_E2E_PROJECT_ID= | ||
| SANITY_E2E_ORGANIZATION_ID= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import {existsSync, readFileSync} from 'node:fs' | ||
|
|
||
| import {createTmpDir} from '@sanity/cli-test' | ||
| import {afterEach, beforeEach, describe, expect, test} from 'vitest' | ||
|
|
||
| import {getE2EDataset, getE2EOrganizationId, getE2EProjectId, runCli} from '../../helpers/runCli.js' | ||
|
|
||
| const orgId = getE2EOrganizationId() | ||
| const projectId = getE2EProjectId() | ||
| const dataset = getE2EDataset() | ||
|
|
||
| describe('sanity init - app', {timeout: 120_000}, () => { | ||
| let tmp: Awaited<ReturnType<typeof createTmpDir>> | ||
|
|
||
| beforeEach(async () => { | ||
| tmp = await createTmpDir({useSystemTmp: true}) | ||
| }) | ||
|
|
||
| afterEach(async () => { | ||
| await tmp.cleanup() | ||
| }) | ||
|
|
||
| describe.each([ | ||
| {label: 'with -y flag', yFlag: ['-y']}, | ||
| {label: 'unattended (no -y)', yFlag: [] as string[]}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does "unattended" mean in this context? Is it meant to mean "non-interactive terminal"? especially since there are some tests that do have interactions
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be hard to see on github but there are only 2 tests in the describe.each The unattended part without yes comes from is without having
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm probably just confused because of the "complete interactive flow" test, it's nbd |
||
| ])('non-interactive ($label)', ({yFlag}) => { | ||
|
binoy14 marked this conversation as resolved.
|
||
| test('creates app with app-quickstart template', async () => { | ||
| const {error, exitCode, stdout} = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| ...yFlag, | ||
| '--template', | ||
| 'app-quickstart', | ||
| '--organization', | ||
| orgId, | ||
| '--output-path', | ||
| tmp.path, | ||
| '--typescript', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| '--no-git', | ||
| ], | ||
| }) | ||
|
|
||
| if (error) throw error | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| expect(existsSync(`${tmp.path}/src/App.tsx`)).toBe(true) | ||
| expect(existsSync(`${tmp.path}/package.json`)).toBe(true) | ||
|
|
||
| const cliConfig = readFileSync(`${tmp.path}/sanity.cli.ts`, 'utf8') | ||
| expect(cliConfig).toContain('organizationId') | ||
| expect(cliConfig).toContain('entry') | ||
|
|
||
| expect(existsSync(`${tmp.path}/sanity.config.ts`)).toBe(false) | ||
|
|
||
| expect(stdout).toMatch(/app has been scaffolded|Success/i) | ||
| }) | ||
|
|
||
| test('scaffolds app with --project and --dataset', async () => { | ||
| const {error, exitCode} = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| ...yFlag, | ||
| '--template', | ||
| 'app-quickstart', | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| dataset, | ||
| '--output-path', | ||
| tmp.path, | ||
| '--package-manager', | ||
| 'pnpm', | ||
| '--no-git', | ||
| ], | ||
| }) | ||
|
|
||
| if (error) throw error | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| const appTsx = readFileSync(`${tmp.path}/src/App.tsx`, 'utf8') | ||
| expect(appTsx).toContain(projectId) | ||
| expect(appTsx).toContain(dataset) | ||
|
|
||
| const cliConfig = readFileSync(`${tmp.path}/sanity.cli.ts`, 'utf8') | ||
| expect(cliConfig).toContain(orgId) | ||
| }) | ||
| }) | ||
|
|
||
| test('complete interactive flow selects project and dataset', async () => { | ||
| const session = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| '--template', | ||
| 'app-quickstart', | ||
| '--organization', | ||
| orgId, | ||
| '--output-path', | ||
| tmp.path, | ||
| '--no-git', | ||
| '--no-mcp', | ||
| ], | ||
| interactive: true, | ||
| }) | ||
|
|
||
| await session.waitForText(/Configure a project for this app/i) | ||
| await session.selectOption(new RegExp(`\\(${projectId}\\)`)) | ||
|
|
||
| await session.waitForText(/Select dataset to use/i) | ||
| await session.selectOption(dataset) | ||
|
|
||
| await session.waitForText(/Package manager to use/i) | ||
| await session.selectOption('pnpm') | ||
|
|
||
| const exitCode = await session.waitForExit(90_000) | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| expect(existsSync(`${tmp.path}/src/App.tsx`)).toBe(true) | ||
| expect(existsSync(`${tmp.path}/package.json`)).toBe(true) | ||
| expect(existsSync(`${tmp.path}/sanity.cli.ts`)).toBe(true) | ||
|
|
||
| const cliConfig = readFileSync(`${tmp.path}/sanity.cli.ts`, 'utf8') | ||
| expect(cliConfig).toContain('organizationId') | ||
|
|
||
| const output = session.getOutput() | ||
| expect(output).toContain('Your custom app has been scaffolded') | ||
| expect(output).toMatch(/Configured with project .+ and dataset/) | ||
| }) | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| import {existsSync, readFileSync} from 'node:fs' | ||
| import {rm} from 'node:fs/promises' | ||
| import {join} from 'node:path' | ||
|
|
||
| import {testFixture} from '@sanity/cli-test' | ||
| import {afterEach, beforeEach, describe, expect, test} from 'vitest' | ||
|
|
||
| import {getE2EProjectId, runCli} from '../../helpers/runCli.js' | ||
|
|
||
| const projectId = getE2EProjectId() | ||
|
|
||
| describe('sanity init - Next.js integration', {timeout: 120_000}, () => { | ||
| let nextjsDir: string | ||
|
|
||
| beforeEach(async () => { | ||
| nextjsDir = await testFixture('nextjs-app', {useSystemTmp: true}) | ||
| await rm(join(nextjsDir, 'node_modules'), {force: true, recursive: true}) | ||
| }) | ||
|
|
||
| afterEach(async () => { | ||
| if (nextjsDir) await rm(nextjsDir, {force: true, recursive: true}) | ||
| }) | ||
|
|
||
| describe.each([ | ||
| {label: 'with -y flag', yFlag: ['-y']}, | ||
| {label: 'unattended (no -y)', yFlag: [] as string[]}, | ||
| ])('non-interactive ($label)', ({yFlag}) => { | ||
| test('creates sanity config, schema, and cli files with --nextjs-add-config-files', async () => { | ||
| const {error, exitCode} = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| ...yFlag, | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| 'production', | ||
| '--nextjs-add-config-files', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| ], | ||
| cwd: nextjsDir, | ||
| }) | ||
|
|
||
| if (error) throw error | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| expect(existsSync(`${nextjsDir}/sanity.config.ts`)).toBe(true) | ||
| expect(existsSync(`${nextjsDir}/sanity/schemaTypes/index.ts`)).toBe(true) | ||
|
|
||
| const cliConfig = readFileSync(`${nextjsDir}/sanity.cli.ts`, 'utf8') | ||
| expect(cliConfig).toContain('NEXT_PUBLIC_SANITY_PROJECT_ID') | ||
| }) | ||
|
|
||
| test('creates embedded studio route with --nextjs-embed-studio', async () => { | ||
| const {error, exitCode} = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| ...yFlag, | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| 'production', | ||
| '--nextjs-add-config-files', | ||
| '--nextjs-embed-studio', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| ], | ||
| cwd: nextjsDir, | ||
| }) | ||
|
|
||
| if (error) throw error | ||
| expect(exitCode).toBe(0) | ||
| expect(existsSync(`${nextjsDir}/app/studio/[[...tool]]/page.tsx`)).toBe(true) | ||
| }) | ||
|
|
||
| test('writes env variables to .env.local with --nextjs-append-env', async () => { | ||
| const {error, exitCode} = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| ...yFlag, | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| 'production', | ||
| '--nextjs-add-config-files', | ||
| '--nextjs-append-env', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| ], | ||
| cwd: nextjsDir, | ||
| }) | ||
|
|
||
| if (error) throw error | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| const envContent = readFileSync(`${nextjsDir}/.env.local`, 'utf8') | ||
| expect(envContent).toContain('NEXT_PUBLIC_SANITY_PROJECT_ID') | ||
| expect(envContent).toContain('NEXT_PUBLIC_SANITY_DATASET') | ||
| }) | ||
| }) | ||
|
|
||
| describe('interactive', () => { | ||
| test('detects Next.js and completes with config files', async () => { | ||
| const session = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| 'production', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| '--no-mcp', | ||
| '--no-git', | ||
| ], | ||
| cwd: nextjsDir, | ||
| interactive: true, | ||
| }) | ||
|
|
||
| await session.waitForText(/Would you like to add configuration files/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Do you want to use TypeScript/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Would you like an embedded Sanity Studio/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/What route do you want to use for the Studio/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Select project template to use/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Would you like to add the project ID and dataset/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| const exitCode = await session.waitForExit(90_000) | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| expect(existsSync(`${nextjsDir}/sanity.config.ts`)).toBe(true) | ||
|
|
||
| const output = session.getOutput() | ||
| expect(output).toMatch(/\/studio/i) | ||
| expect(output).not.toMatch(/Project output path/i) | ||
| }) | ||
|
|
||
| test('accepts custom studio route', async () => { | ||
| const session = await runCli({ | ||
| args: [ | ||
| 'init', | ||
| '--project', | ||
| projectId, | ||
| '--dataset', | ||
| 'production', | ||
| '--package-manager', | ||
| 'pnpm', | ||
| '--no-mcp', | ||
| '--no-git', | ||
| ], | ||
| cwd: nextjsDir, | ||
| interactive: true, | ||
| }) | ||
|
|
||
| await session.waitForText(/Would you like to add configuration files/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Do you want to use TypeScript/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Would you like an embedded Sanity Studio/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/What route do you want to use for the Studio/i) | ||
| session.write('/admin\n') | ||
|
|
||
| await session.waitForText(/Select project template to use/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| await session.waitForText(/Would you like to add the project ID and dataset/i) | ||
| session.sendKey('Enter') | ||
|
|
||
| const exitCode = await session.waitForExit(90_000) | ||
| expect(exitCode).toBe(0) | ||
|
|
||
| expect(existsSync(`${nextjsDir}/app/admin/[[...tool]]/page.tsx`)).toBe(true) | ||
| expect(existsSync(`${nextjsDir}/sanity.config.ts`)).toBe(true) | ||
| }) | ||
| }) | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.