Skip to content

Commit c6b4c2a

Browse files
committed
chore: more unit test coverage for the engine
1 parent f5ba500 commit c6b4c2a

15 files changed

+770
-22
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ my-app
1212
.nx/cache
1313
.nx/workspace-data
1414

15-
.content-collections
15+
.content-collections
16+
17+
coverage

packages/cta-engine/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"build": "tsc",
1010
"dev": "tsc --watch",
1111
"test": "eslint ./src && vitest run",
12-
"test:watch": "vitest"
12+
"test:watch": "vitest",
13+
"test:coverage": "vitest run --coverage"
1314
},
1415
"repository": {
1516
"type": "git",
@@ -38,6 +39,7 @@
3839
"@tanstack/config": "^0.16.2",
3940
"@types/ejs": "^3.1.5",
4041
"@types/node": "^22.13.4",
42+
"@vitest/coverage-v8": "3.1.1",
4143
"eslint": "^9.20.0",
4244
"typescript": "^5.6.3",
4345
"vitest": "^3.0.8"

packages/cta-engine/src/package-json.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ function mergePackageJSON(
2525
}
2626

2727
export function createPackageJSON(options: Options) {
28-
const addOns = options.chosenAddOns.map((addOn) => addOn.packageAdditions)
29-
3028
let packageJSON = {
3129
...JSON.parse(JSON.stringify(options.framework.basePackageJSON)),
3230
name: options.projectName,
@@ -47,15 +45,17 @@ export function createPackageJSON(options: Options) {
4745
packageJSON = mergePackageJSON(packageJSON, addition!)
4846
}
4947

50-
for (const addOn of addOns) {
48+
for (const addOn of options.chosenAddOns.map(
49+
(addOn) => addOn.packageAdditions,
50+
)) {
5151
packageJSON = mergePackageJSON(packageJSON, addOn)
5252
}
5353

5454
packageJSON.dependencies = sortObject(
55-
packageJSON.dependencies as Record<string, string>,
55+
(packageJSON.dependencies ?? {}) as Record<string, string>,
5656
)
5757
packageJSON.devDependencies = sortObject(
58-
packageJSON.devDependencies as Record<string, string>,
58+
(packageJSON.devDependencies ?? {}) as Record<string, string>,
5959
)
6060

6161
return packageJSON

packages/cta-engine/src/template-file.ts

+29-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { format } from 'prettier'
55
import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
66
import { formatCommand } from './utils.js'
77
import {
8-
getPackageManagerExecuteCommand,
98
getPackageManagerInstallCommand,
9+
getPackageManagerScriptCommand,
1010
} from './package-manager.js'
1111
import { relativePath } from './file-helpers.js'
1212

@@ -36,9 +36,15 @@ export function createTemplateFile(
3636
),
3737
)
3838
}
39-
function getPackageManagerRunScript(scriptName: string) {
39+
function getPackageManagerRunScript(
40+
scriptName: string,
41+
args: Array<string> = [],
42+
) {
4043
return formatCommand(
41-
getPackageManagerExecuteCommand(options.packageManager, scriptName),
44+
getPackageManagerScriptCommand(options.packageManager, [
45+
scriptName,
46+
...args,
47+
]),
4248
)
4349
}
4450

@@ -65,6 +71,24 @@ export function createTemplateFile(
6571
}
6672
}
6773

74+
const variables = {
75+
...options.variableValues,
76+
...options.chosenAddOns.reduce<Record<string, any>>((acc, addOn) => {
77+
return {
78+
...acc,
79+
...addOn.variables,
80+
}
81+
}, {}),
82+
}
83+
84+
const addOnEnabled = options.chosenAddOns.reduce<Record<string, boolean>>(
85+
(acc, addOn) => {
86+
acc[addOn.id] = true
87+
return acc
88+
},
89+
{},
90+
)
91+
6892
return async function templateFile(file: string, content: string) {
6993
const templateValues = {
7094
packageManager: options.packageManager,
@@ -75,16 +99,11 @@ export function createTemplateFile(
7599
jsx: options.typescript ? 'tsx' : 'jsx',
76100
fileRouter: options.mode === FILE_ROUTER,
77101
codeRouter: options.mode === CODE_ROUTER,
78-
addOnEnabled: options.chosenAddOns.reduce<Record<string, boolean>>(
79-
(acc, addOn) => {
80-
acc[addOn.id] = true
81-
return acc
82-
},
83-
{},
84-
),
102+
addOnEnabled,
85103
addOns: options.chosenAddOns,
86104
integrations,
87105
routes,
106+
variables,
88107

89108
getPackageManagerAddScript,
90109
getPackageManagerRunScript,

packages/cta-engine/src/types.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ export type FileBundleHandler = {
1010
getFileContents: (path: string) => Promise<string>
1111
}
1212

13+
export type Integration = {
14+
type: 'provider' | 'root-provider' | 'layout' | 'header-user'
15+
path: string
16+
jsName: string
17+
}
18+
1319
export type AddOnDefinition = {
1420
id: string
1521
name: string
@@ -37,11 +43,7 @@ export type AddOnDefinition = {
3743
shadcnComponents?: Array<string>
3844
warning?: string
3945
dependsOn?: Array<string>
40-
integrations?: Array<{
41-
type: 'provider' | 'root-provider' | 'layout' | 'header-user'
42-
path: string
43-
jsName: string
44-
}>
46+
integrations?: Array<Integration>
4547
variables?: Array<Variable>
4648

4749
files?: Record<string, string>
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { finalizeAddOns, getAllAddOns } from '../src/add-ons.js'
4+
5+
import type { AddOn, Framework } from '../src/types.js'
6+
7+
describe('getAllAddOns', () => {
8+
it('filter add-ons', () => {
9+
const addOns = getAllAddOns(
10+
{
11+
id: 'react-cra',
12+
getAddOns: () => [
13+
{
14+
id: 'add-on-1',
15+
description: 'Add-on 1',
16+
templates: ['file-router'],
17+
} as AddOn,
18+
{
19+
id: 'add-on-2',
20+
description: 'Add-on 2',
21+
templates: ['code-router'],
22+
} as AddOn,
23+
],
24+
} as Framework,
25+
'file-router',
26+
)
27+
28+
expect(addOns.length).toEqual(1)
29+
expect(addOns[0].id).toEqual('add-on-1')
30+
})
31+
})
32+
33+
describe('finalizeAddOns', () => {
34+
it('should finalize add-ons', async () => {
35+
const addOns = await finalizeAddOns(
36+
{
37+
id: 'react-cra',
38+
getAddOns: () => [
39+
{
40+
id: 'add-on-1',
41+
description: 'Add-on 1',
42+
templates: ['file-router'],
43+
dependsOn: ['add-on-2'],
44+
} as AddOn,
45+
{
46+
id: 'add-on-2',
47+
description: 'Add-on 2',
48+
templates: ['file-router'],
49+
} as AddOn,
50+
{
51+
id: 'add-on-3',
52+
description: 'Add-on 3',
53+
templates: ['file-router'],
54+
} as AddOn,
55+
],
56+
} as Framework,
57+
'file-router',
58+
['add-on-1'],
59+
)
60+
61+
expect(addOns.length).toEqual(2)
62+
const addOnIds = addOns.map((a) => a.id)
63+
expect(addOnIds.includes('add-on-1')).toEqual(true)
64+
expect(addOnIds.includes('add-on-2')).toEqual(true)
65+
expect(addOnIds.includes('add-on-3')).toEqual(false)
66+
})
67+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { resolve } from 'node:path'
3+
4+
import { writeConfigFile } from '../src/config-file.js'
5+
import { CONFIG_FILE } from '../src/constants.js'
6+
7+
import type { AddOn, Environment, Framework, Options } from '../src/types.js'
8+
9+
describe('writeConfigFile', () => {
10+
it('should write the config file', async () => {
11+
const options = {
12+
framework: {
13+
id: 'react-cra',
14+
getAddOns: () => [],
15+
} as unknown as Framework,
16+
chosenAddOns: [
17+
{
18+
id: 'add-on-1',
19+
description: 'Add-on 1',
20+
templates: ['file-router'],
21+
} as AddOn,
22+
],
23+
addOns: [],
24+
} as unknown as Options
25+
const targetDir = 'test-dir'
26+
const persistedOptions = {
27+
version: 1,
28+
framework: options.framework.id,
29+
existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
30+
}
31+
const env = {
32+
writeFile: (path, optionsString) => {
33+
expect(path).toEqual(resolve(targetDir, CONFIG_FILE))
34+
expect(optionsString).toEqual(JSON.stringify(persistedOptions, null, 2))
35+
},
36+
} as Environment
37+
await writeConfigFile(env, targetDir, options)
38+
})
39+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { createApp } from '../src/create-app.js'
3+
4+
import { createMemoryEnvironment } from '../src/environment.js'
5+
import { FILE_ROUTER } from '../src/constants.js'
6+
import { Options } from '../src/types.js'
7+
8+
const simpleOptions = {
9+
projectName: 'test',
10+
framework: {
11+
id: 'test',
12+
name: 'Test',
13+
basePackageJSON: {
14+
scripts: {
15+
dev: 'react-scripts start',
16+
},
17+
},
18+
optionalPackages: {
19+
typescript: {
20+
devDependencies: {
21+
typescript: '^5.0.0',
22+
},
23+
},
24+
tailwindcss: {
25+
dependencies: {
26+
tailwindcss: '^3.0.0',
27+
},
28+
},
29+
'file-router': {
30+
dependencies: {
31+
'file-router': '^1.0.0',
32+
},
33+
},
34+
},
35+
getFiles: () => ['./src/test.txt'],
36+
getFileContents: () => 'Hello',
37+
},
38+
chosenAddOns: [],
39+
packageManager: 'pnpm',
40+
typescript: true,
41+
tailwind: true,
42+
mode: FILE_ROUTER,
43+
variableValues: {},
44+
} as unknown as Options
45+
46+
describe('createApp', () => {
47+
it('should create an app', async () => {
48+
const { environment, output } = createMemoryEnvironment()
49+
await createApp(simpleOptions, {
50+
silent: true,
51+
environment,
52+
name: 'Test',
53+
cwd: '/',
54+
appName: 'TanStack App',
55+
})
56+
57+
expect(output.files['/src/test.txt']).toEqual('Hello')
58+
})
59+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { createMemoryEnvironment } from '../src/environment.js'
4+
5+
describe('createMemoryEnvironment', () => {
6+
it('should handle basic file operations', async () => {
7+
const { environment, output } = createMemoryEnvironment()
8+
9+
environment.startRun()
10+
await environment.writeFile('/test.txt', 'test')
11+
environment.finishRun()
12+
13+
expect(output.files['/test.txt']).toEqual('test')
14+
})
15+
16+
it('should track command execution', async () => {
17+
const { environment, output } = createMemoryEnvironment()
18+
19+
environment.startRun()
20+
await environment.execute('echo', ['test'], '')
21+
environment.finishRun()
22+
23+
expect(output.commands.length).toEqual(1)
24+
expect(output.commands[0].command).toEqual('echo')
25+
expect(output.commands[0].args).toEqual(['test'])
26+
})
27+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { createMemoryEnvironment } from '../../src/environment.js'
4+
import { setupGit } from '../../src/integrations/git.js'
5+
6+
describe('git', () => {
7+
it('should create a git repository', async () => {
8+
const { environment, output } = createMemoryEnvironment()
9+
environment.startRun()
10+
await setupGit(environment, '/test')
11+
environment.finishRun()
12+
13+
expect(output.commands).toEqual([
14+
{
15+
command: 'git',
16+
args: ['init'],
17+
},
18+
])
19+
})
20+
})

0 commit comments

Comments
 (0)