create-ripple 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/package.json +51 -0
- package/src/commands/create.js +144 -0
- package/src/constants.js +17 -0
- package/src/index.js +52 -0
- package/src/lib/project-creator.js +243 -0
- package/src/lib/prompts.js +136 -0
- package/src/lib/templates.js +97 -0
- package/src/lib/validation.js +155 -0
- package/tests/integration/cli.test.js +179 -0
- package/tests/integration/project-creator.test.js +230 -0
- package/tests/unit/prompts.test.js +207 -0
- package/tests/unit/templates.test.js +160 -0
- package/tests/unit/validation.test.js +192 -0
- package/vitest.config.js +22 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { createProject } from '../../src/lib/project-creator.js';
|
|
6
|
+
import { getLocalTemplatePath, isLocalDevelopment, validateTemplate } from '../../src/lib/templates.js';
|
|
7
|
+
|
|
8
|
+
// Mock ora for cleaner test output
|
|
9
|
+
vi.mock('ora', () => ({
|
|
10
|
+
default: () => ({
|
|
11
|
+
start: () => ({ succeed: vi.fn(), fail: vi.fn(), warn: vi.fn() }),
|
|
12
|
+
succeed: vi.fn(),
|
|
13
|
+
fail: vi.fn(),
|
|
14
|
+
warn: vi.fn()
|
|
15
|
+
})
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock execSync to prevent actual git commands during tests
|
|
19
|
+
vi.mock('node:child_process', () => ({
|
|
20
|
+
default: {
|
|
21
|
+
execSync: vi.fn()
|
|
22
|
+
},
|
|
23
|
+
execSync: vi.fn()
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock degit to prevent actual network calls
|
|
27
|
+
vi.mock('degit', () => ({
|
|
28
|
+
default: vi.fn(() => ({
|
|
29
|
+
clone: vi.fn().mockResolvedValue(undefined)
|
|
30
|
+
}))
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock template functions globally
|
|
34
|
+
vi.mock('../../src/lib/templates.js', () => ({
|
|
35
|
+
getLocalTemplatePath: vi.fn(),
|
|
36
|
+
isLocalDevelopment: vi.fn(),
|
|
37
|
+
downloadTemplate: vi.fn(),
|
|
38
|
+
validateTemplate: vi.fn()
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
describe('createProject integration tests', () => {
|
|
42
|
+
let testDir;
|
|
43
|
+
let projectPath;
|
|
44
|
+
let templatePath;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// Create a temporary test directory
|
|
48
|
+
testDir = join(tmpdir(), `create-ripple-test-${Date.now()}`);
|
|
49
|
+
mkdirSync(testDir, { recursive: true });
|
|
50
|
+
|
|
51
|
+
projectPath = join(testDir, 'test-project');
|
|
52
|
+
templatePath = join(testDir, 'template');
|
|
53
|
+
|
|
54
|
+
// Create a mock template directory structure
|
|
55
|
+
mkdirSync(templatePath, { recursive: true });
|
|
56
|
+
mkdirSync(join(templatePath, 'src'), { recursive: true });
|
|
57
|
+
|
|
58
|
+
// Create mock template files
|
|
59
|
+
writeFileSync(
|
|
60
|
+
join(templatePath, 'package.json'),
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
name: 'vite-template-ripple',
|
|
63
|
+
version: '0.0.0',
|
|
64
|
+
type: 'module',
|
|
65
|
+
scripts: {
|
|
66
|
+
dev: 'vite',
|
|
67
|
+
build: 'vite build'
|
|
68
|
+
},
|
|
69
|
+
dependencies: {
|
|
70
|
+
ripple: '^0.2.29'
|
|
71
|
+
},
|
|
72
|
+
devDependencies: {
|
|
73
|
+
'vite-plugin-ripple': '^0.2.29',
|
|
74
|
+
prettier: '^3.6.2'
|
|
75
|
+
}
|
|
76
|
+
}, null, 2)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
writeFileSync(join(templatePath, 'index.html'), '<!DOCTYPE html><html></html>');
|
|
80
|
+
writeFileSync(join(templatePath, 'src', 'App.ripple'), '<h1>Hello Ripple!</h1>');
|
|
81
|
+
writeFileSync(join(templatePath, 'README.md'), '# Template Project');
|
|
82
|
+
|
|
83
|
+
// Set up mocks for each test
|
|
84
|
+
vi.mocked(getLocalTemplatePath).mockReturnValue(templatePath);
|
|
85
|
+
vi.mocked(isLocalDevelopment).mockReturnValue(true);
|
|
86
|
+
vi.mocked(validateTemplate).mockReturnValue(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
// Clean up test directory
|
|
91
|
+
if (existsSync(testDir)) {
|
|
92
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
vi.clearAllMocks();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should create a project successfully', async () => {
|
|
98
|
+
await createProject({
|
|
99
|
+
projectName: 'test-project',
|
|
100
|
+
projectPath,
|
|
101
|
+
template: 'basic',
|
|
102
|
+
packageManager: 'npm',
|
|
103
|
+
typescript: true,
|
|
104
|
+
gitInit: false
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Verify project directory was created
|
|
108
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
109
|
+
|
|
110
|
+
// Verify files were copied
|
|
111
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
112
|
+
expect(existsSync(join(projectPath, 'index.html'))).toBe(true);
|
|
113
|
+
expect(existsSync(join(projectPath, 'src', 'App.ripple'))).toBe(true);
|
|
114
|
+
expect(existsSync(join(projectPath, 'README.md'))).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Verify package.json was updated
|
|
117
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
118
|
+
expect(packageJson.name).toBe('test-project');
|
|
119
|
+
expect(packageJson.description).toBe('A Ripple application created with create-ripple-app');
|
|
120
|
+
expect(packageJson.version).toBe('1.0.0');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should update package.json with correct package manager', async () => {
|
|
124
|
+
await createProject({
|
|
125
|
+
projectName: 'test-pnpm-project',
|
|
126
|
+
projectPath,
|
|
127
|
+
template: 'basic',
|
|
128
|
+
packageManager: 'pnpm',
|
|
129
|
+
typescript: true,
|
|
130
|
+
gitInit: false
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
134
|
+
expect(packageJson.packageManager).toBe('pnpm@9.0.0');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should not add packageManager field for npm', async () => {
|
|
138
|
+
await createProject({
|
|
139
|
+
projectName: 'test-npm-project',
|
|
140
|
+
projectPath,
|
|
141
|
+
template: 'basic',
|
|
142
|
+
packageManager: 'npm',
|
|
143
|
+
typescript: true,
|
|
144
|
+
gitInit: false
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
148
|
+
expect(packageJson.packageManager).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should update dependency versions', async () => {
|
|
152
|
+
await createProject({
|
|
153
|
+
projectName: 'test-deps-project',
|
|
154
|
+
projectPath,
|
|
155
|
+
template: 'basic',
|
|
156
|
+
packageManager: 'npm',
|
|
157
|
+
typescript: true,
|
|
158
|
+
gitInit: false
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const packageJson = JSON.parse(readFileSync(join(projectPath, 'package.json'), 'utf-8'));
|
|
162
|
+
expect(packageJson.dependencies.ripple).toBe('^0.2.35');
|
|
163
|
+
expect(packageJson.devDependencies['vite-plugin-ripple']).toBe('^0.2.29');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle missing template directory', async () => {
|
|
167
|
+
const invalidTemplatePath = join(testDir, 'non-existent-template');
|
|
168
|
+
|
|
169
|
+
// Override the mock for this specific test
|
|
170
|
+
vi.mocked(getLocalTemplatePath).mockReturnValue(invalidTemplatePath);
|
|
171
|
+
|
|
172
|
+
await expect(
|
|
173
|
+
createProject({
|
|
174
|
+
projectName: 'test-project',
|
|
175
|
+
projectPath,
|
|
176
|
+
template: 'basic',
|
|
177
|
+
packageManager: 'npm',
|
|
178
|
+
typescript: true,
|
|
179
|
+
gitInit: false
|
|
180
|
+
})
|
|
181
|
+
).rejects.toThrow('Local template "basic" not found');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should filter out unwanted files during copy', async () => {
|
|
185
|
+
// Add files that should be filtered out
|
|
186
|
+
mkdirSync(join(templatePath, 'node_modules'), { recursive: true });
|
|
187
|
+
writeFileSync(join(templatePath, 'node_modules', 'some-package.js'), 'module content');
|
|
188
|
+
writeFileSync(join(templatePath, 'package-lock.json'), '{}');
|
|
189
|
+
writeFileSync(join(templatePath, 'yarn.lock'), 'yarn lock content');
|
|
190
|
+
writeFileSync(join(templatePath, 'pnpm-lock.yaml'), 'pnpm lock content');
|
|
191
|
+
|
|
192
|
+
await createProject({
|
|
193
|
+
projectName: 'test-filter-project',
|
|
194
|
+
projectPath,
|
|
195
|
+
template: 'basic',
|
|
196
|
+
packageManager: 'npm',
|
|
197
|
+
typescript: true,
|
|
198
|
+
gitInit: false
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Verify filtered files were not copied
|
|
202
|
+
expect(existsSync(join(projectPath, 'node_modules'))).toBe(false);
|
|
203
|
+
expect(existsSync(join(projectPath, 'package-lock.json'))).toBe(false);
|
|
204
|
+
expect(existsSync(join(projectPath, 'yarn.lock'))).toBe(false);
|
|
205
|
+
expect(existsSync(join(projectPath, 'pnpm-lock.yaml'))).toBe(false);
|
|
206
|
+
|
|
207
|
+
// Verify other files were copied
|
|
208
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
209
|
+
expect(existsSync(join(projectPath, 'index.html'))).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should handle project creation in existing directory', async () => {
|
|
213
|
+
// Create the directory first
|
|
214
|
+
mkdirSync(projectPath, { recursive: true });
|
|
215
|
+
writeFileSync(join(projectPath, 'existing-file.txt'), 'existing content');
|
|
216
|
+
|
|
217
|
+
await createProject({
|
|
218
|
+
projectName: 'test-existing-project',
|
|
219
|
+
projectPath,
|
|
220
|
+
template: 'basic',
|
|
221
|
+
packageManager: 'npm',
|
|
222
|
+
typescript: true,
|
|
223
|
+
gitInit: false
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Verify project was created successfully
|
|
227
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
228
|
+
expect(existsSync(join(projectPath, 'existing-file.txt'))).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import * as prompts from 'prompts';
|
|
3
|
+
|
|
4
|
+
// Mock prompts module
|
|
5
|
+
vi.mock('prompts', () => ({
|
|
6
|
+
default: vi.fn()
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
// Mock kleur colors
|
|
10
|
+
vi.mock('kleur/colors', () => ({
|
|
11
|
+
red: vi.fn((text) => text)
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Mock process.exit
|
|
15
|
+
const mockExit = vi.fn();
|
|
16
|
+
Object.defineProperty(process, 'exit', {
|
|
17
|
+
value: mockExit,
|
|
18
|
+
writable: true
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Mock console.log
|
|
22
|
+
const mockConsoleLog = vi.fn();
|
|
23
|
+
global.console = { ...console, log: mockConsoleLog };
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
promptProjectName,
|
|
27
|
+
promptTemplate,
|
|
28
|
+
promptOverwrite,
|
|
29
|
+
promptPackageManager,
|
|
30
|
+
promptTypeScript,
|
|
31
|
+
promptGitInit
|
|
32
|
+
} from '../../src/lib/prompts.js';
|
|
33
|
+
|
|
34
|
+
describe('Prompts', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.resetAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('promptProjectName', () => {
|
|
44
|
+
it('should return project name when valid input provided', async () => {
|
|
45
|
+
prompts.default.mockResolvedValue({ projectName: 'my-app' });
|
|
46
|
+
|
|
47
|
+
const result = await promptProjectName();
|
|
48
|
+
expect(result).toBe('my-app');
|
|
49
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
50
|
+
type: 'text',
|
|
51
|
+
name: 'projectName',
|
|
52
|
+
message: 'What is your project named?',
|
|
53
|
+
initial: 'my-ripple-app',
|
|
54
|
+
validate: expect.any(Function)
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should use custom default name', async () => {
|
|
59
|
+
prompts.default.mockResolvedValue({ projectName: 'custom-app' });
|
|
60
|
+
|
|
61
|
+
await promptProjectName('custom-default');
|
|
62
|
+
expect(prompts.default).toHaveBeenCalledWith(
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
initial: 'custom-default'
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should exit when user cancels', async () => {
|
|
70
|
+
prompts.default.mockResolvedValue({});
|
|
71
|
+
|
|
72
|
+
await promptProjectName();
|
|
73
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
74
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('✖ Operation cancelled');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should validate project name input', async () => {
|
|
78
|
+
prompts.default.mockResolvedValue({ projectName: 'valid-name' });
|
|
79
|
+
|
|
80
|
+
await promptProjectName();
|
|
81
|
+
const call = prompts.default.mock.calls[0][0];
|
|
82
|
+
const validate = call.validate;
|
|
83
|
+
|
|
84
|
+
expect(validate('valid-name')).toBe(true);
|
|
85
|
+
expect(validate('Invalid Name!')).toBe(
|
|
86
|
+
'Project name can only contain lowercase letters, numbers, hyphens, dots, and underscores'
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('promptTemplate', () => {
|
|
92
|
+
it('should return selected template', async () => {
|
|
93
|
+
prompts.default.mockResolvedValue({ template: 'basic' });
|
|
94
|
+
|
|
95
|
+
const result = await promptTemplate();
|
|
96
|
+
expect(result).toBe('basic');
|
|
97
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
98
|
+
type: 'select',
|
|
99
|
+
name: 'template',
|
|
100
|
+
message: 'Which template would you like to use?',
|
|
101
|
+
choices: expect.any(Array),
|
|
102
|
+
initial: 0
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should exit when user cancels', async () => {
|
|
107
|
+
prompts.default.mockResolvedValue({});
|
|
108
|
+
|
|
109
|
+
await promptTemplate();
|
|
110
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
111
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('✖ Operation cancelled');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('promptOverwrite', () => {
|
|
116
|
+
it('should return overwrite decision', async () => {
|
|
117
|
+
prompts.default.mockResolvedValue({ overwrite: true });
|
|
118
|
+
|
|
119
|
+
const result = await promptOverwrite('test-project');
|
|
120
|
+
expect(result).toBe(true);
|
|
121
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'overwrite',
|
|
124
|
+
message: 'Directory "test-project" already exists. Continue anyway?',
|
|
125
|
+
initial: false
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should exit when user cancels', async () => {
|
|
130
|
+
prompts.default.mockResolvedValue({});
|
|
131
|
+
|
|
132
|
+
await promptOverwrite('test-project');
|
|
133
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('promptPackageManager', () => {
|
|
138
|
+
it('should return selected package manager', async () => {
|
|
139
|
+
prompts.default.mockResolvedValue({ packageManager: 'pnpm' });
|
|
140
|
+
|
|
141
|
+
const result = await promptPackageManager();
|
|
142
|
+
expect(result).toBe('pnpm');
|
|
143
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
144
|
+
type: 'select',
|
|
145
|
+
name: 'packageManager',
|
|
146
|
+
message: 'Which package manager would you like to use?',
|
|
147
|
+
choices: [
|
|
148
|
+
{ title: 'npm', value: 'npm', description: 'Use npm for dependency management' },
|
|
149
|
+
{ title: 'yarn', value: 'yarn', description: 'Use Yarn for dependency management' },
|
|
150
|
+
{ title: 'pnpm', value: 'pnpm', description: 'Use pnpm for dependency management' }
|
|
151
|
+
],
|
|
152
|
+
initial: 0
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should exit when user cancels', async () => {
|
|
157
|
+
prompts.default.mockResolvedValue({});
|
|
158
|
+
|
|
159
|
+
await promptPackageManager();
|
|
160
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('promptTypeScript', () => {
|
|
165
|
+
it('should return TypeScript preference', async () => {
|
|
166
|
+
prompts.default.mockResolvedValue({ typescript: false });
|
|
167
|
+
|
|
168
|
+
const result = await promptTypeScript();
|
|
169
|
+
expect(result).toBe(false);
|
|
170
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
171
|
+
type: 'confirm',
|
|
172
|
+
name: 'typescript',
|
|
173
|
+
message: 'Would you like to use TypeScript?',
|
|
174
|
+
initial: true
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should exit when user cancels', async () => {
|
|
179
|
+
prompts.default.mockResolvedValue({});
|
|
180
|
+
|
|
181
|
+
await promptTypeScript();
|
|
182
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('promptGitInit', () => {
|
|
187
|
+
it('should return Git initialization preference', async () => {
|
|
188
|
+
prompts.default.mockResolvedValue({ gitInit: false });
|
|
189
|
+
|
|
190
|
+
const result = await promptGitInit();
|
|
191
|
+
expect(result).toBe(false);
|
|
192
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
193
|
+
type: 'confirm',
|
|
194
|
+
name: 'gitInit',
|
|
195
|
+
message: 'Initialize a new Git repository?',
|
|
196
|
+
initial: true
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should exit when user cancels', async () => {
|
|
201
|
+
prompts.default.mockResolvedValue({});
|
|
202
|
+
|
|
203
|
+
await promptGitInit();
|
|
204
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
getTemplate,
|
|
5
|
+
getTemplateNames,
|
|
6
|
+
getTemplateChoices,
|
|
7
|
+
validateTemplate,
|
|
8
|
+
getLocalTemplatePath,
|
|
9
|
+
isLocalDevelopment,
|
|
10
|
+
downloadTemplate
|
|
11
|
+
} from '../../src/lib/templates.js';
|
|
12
|
+
|
|
13
|
+
// Mock the constants
|
|
14
|
+
vi.mock('../../src/constants.js', () => ({
|
|
15
|
+
TEMPLATES: [
|
|
16
|
+
{
|
|
17
|
+
name: 'basic',
|
|
18
|
+
display: 'Basic Ripple App',
|
|
19
|
+
description: 'A minimal Ripple application with Vite and TypeScript'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'advanced',
|
|
23
|
+
display: 'Advanced Ripple App',
|
|
24
|
+
description: 'A full-featured Ripple application'
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
TEMPLATES_DIR: '/mock/templates'
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Mock fs.existsSync - ensure consistent behavior across environments
|
|
31
|
+
vi.mock('node:fs', () => {
|
|
32
|
+
const mockFn = vi.fn();
|
|
33
|
+
return {
|
|
34
|
+
default: {
|
|
35
|
+
existsSync: mockFn
|
|
36
|
+
},
|
|
37
|
+
existsSync: mockFn
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getTemplate', () => {
|
|
42
|
+
it('should return template by name', () => {
|
|
43
|
+
const template = getTemplate('basic');
|
|
44
|
+
expect(template).toEqual({
|
|
45
|
+
name: 'basic',
|
|
46
|
+
display: 'Basic Ripple App',
|
|
47
|
+
description: 'A minimal Ripple application with Vite and TypeScript'
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return null for non-existent template', () => {
|
|
52
|
+
const template = getTemplate('non-existent');
|
|
53
|
+
expect(template).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return null for undefined template name', () => {
|
|
57
|
+
const template = getTemplate();
|
|
58
|
+
expect(template).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getTemplateNames', () => {
|
|
63
|
+
it('should return array of template names', () => {
|
|
64
|
+
const names = getTemplateNames();
|
|
65
|
+
expect(names).toEqual(['basic', 'advanced']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return array even if no templates exist', () => {
|
|
69
|
+
const names = getTemplateNames();
|
|
70
|
+
expect(Array.isArray(names)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('getTemplateChoices', () => {
|
|
75
|
+
it('should return formatted choices for prompts', () => {
|
|
76
|
+
const choices = getTemplateChoices();
|
|
77
|
+
expect(choices).toEqual([
|
|
78
|
+
{
|
|
79
|
+
title: 'Basic Ripple App',
|
|
80
|
+
description: 'A minimal Ripple application with Vite and TypeScript',
|
|
81
|
+
value: 'basic'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: 'Advanced Ripple App',
|
|
85
|
+
description: 'A full-featured Ripple application',
|
|
86
|
+
value: 'advanced'
|
|
87
|
+
}
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return array even if templates are defined', () => {
|
|
92
|
+
const choices = getTemplateChoices();
|
|
93
|
+
expect(Array.isArray(choices)).toBe(true);
|
|
94
|
+
expect(choices.length).toBeGreaterThan(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('validateTemplate', () => {
|
|
99
|
+
it('should return true for valid template', () => {
|
|
100
|
+
const isValid = validateTemplate('basic');
|
|
101
|
+
expect(isValid).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return false for invalid template name', () => {
|
|
105
|
+
const isValid = validateTemplate('non-existent');
|
|
106
|
+
expect(isValid).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return false for undefined template name', () => {
|
|
110
|
+
const isValid = validateTemplate();
|
|
111
|
+
expect(isValid).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should return false for null template name', () => {
|
|
115
|
+
const isValid = validateTemplate(null);
|
|
116
|
+
expect(isValid).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return false for empty string template name', () => {
|
|
120
|
+
const isValid = validateTemplate('');
|
|
121
|
+
expect(isValid).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getLocalTemplatePath', () => {
|
|
126
|
+
it('should return correct local template path', () => {
|
|
127
|
+
const path = getLocalTemplatePath('basic');
|
|
128
|
+
expect(path).toContain('templates/basic');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return path even for non-existent template', () => {
|
|
132
|
+
const path = getLocalTemplatePath('non-existent');
|
|
133
|
+
expect(path).toContain('templates/non-existent');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle special characters in template name', () => {
|
|
137
|
+
const path = getLocalTemplatePath('my-template.name');
|
|
138
|
+
expect(path).toContain('templates/my-template.name');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('isLocalDevelopment', () => {
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
vi.clearAllMocks();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return true when templates directory exists', () => {
|
|
148
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
149
|
+
mockExistsSync.mockReturnValue(true);
|
|
150
|
+
const isDev = isLocalDevelopment();
|
|
151
|
+
expect(isDev).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return false when templates directory does not exist', () => {
|
|
155
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
156
|
+
mockExistsSync.mockReturnValue(false);
|
|
157
|
+
const isDev = isLocalDevelopment();
|
|
158
|
+
expect(isDev).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
});
|