chore: bump version to 2.80.27 and update core features
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Unit tests for FileSystemProjectScaffolder.
|
||||
*
|
||||
* Drives against a real temp directory so end-to-end file IO + path-traversal
|
||||
* defenses are exercised.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
FileSystemProjectScaffolder,
|
||||
validateProjectName,
|
||||
} from '../src/scaffolder/projectScaffolder';
|
||||
|
||||
function tmp(): string {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), 'astra-scaffold-test-'));
|
||||
}
|
||||
|
||||
describe('validateProjectName', () => {
|
||||
test('accepts allowed names', () => {
|
||||
expect(validateProjectName('foo')).toBe('foo');
|
||||
expect(validateProjectName('foo-bar_v2')).toBe('foo-bar_v2');
|
||||
expect(validateProjectName(' trimmed ')).toBe('trimmed');
|
||||
});
|
||||
|
||||
test('rejects too short', () => {
|
||||
expect(validateProjectName('a')).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects too long', () => {
|
||||
expect(validateProjectName('a'.repeat(41))).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects invalid chars', () => {
|
||||
expect(validateProjectName('foo bar')).toBeNull();
|
||||
expect(validateProjectName('foo/bar')).toBeNull();
|
||||
expect(validateProjectName('foo.bar')).toBeNull();
|
||||
expect(validateProjectName('한글이름')).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects empty / non-string', () => {
|
||||
expect(validateProjectName('')).toBeNull();
|
||||
expect(validateProjectName(undefined as any)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileSystemProjectScaffolder', () => {
|
||||
let root: string;
|
||||
let scaffolder: FileSystemProjectScaffolder;
|
||||
|
||||
beforeEach(() => {
|
||||
root = tmp();
|
||||
scaffolder = new FileSystemProjectScaffolder();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||
});
|
||||
|
||||
test('listTemplates returns the catalog', () => {
|
||||
const list = scaffolder.listTemplates();
|
||||
const ids = list.map(t => t.id);
|
||||
expect(ids).toContain('static');
|
||||
expect(ids).toContain('vite-vanilla');
|
||||
expect(ids).toContain('vite-react');
|
||||
});
|
||||
|
||||
test('static template writes index.html + README.md', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'demo-static', template: 'static', rootDir: root });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(fs.existsSync(path.join(result.projectPath, 'site', 'index.html'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(result.projectPath, 'README.md'))).toBe(true);
|
||||
const html = fs.readFileSync(path.join(result.projectPath, 'site', 'index.html'), 'utf8');
|
||||
expect(html).toContain('demo-static');
|
||||
});
|
||||
|
||||
test('vite-vanilla template writes package.json + main.js', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'vv', template: 'vite-vanilla', rootDir: root });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(result.projectPath, 'site', 'package.json'), 'utf8'));
|
||||
expect(pkg.name).toBe('vv');
|
||||
expect(pkg.devDependencies.vite).toBeDefined();
|
||||
expect(fs.existsSync(path.join(result.projectPath, 'site', 'main.js'))).toBe(true);
|
||||
});
|
||||
|
||||
test('vite-react template includes tsconfig + main.tsx', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'vr', template: 'vite-react', rootDir: root });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(fs.existsSync(path.join(result.projectPath, 'site', 'tsconfig.json'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(result.projectPath, 'site', 'src', 'main.tsx'))).toBe(true);
|
||||
const tsx = fs.readFileSync(path.join(result.projectPath, 'site', 'src', 'main.tsx'), 'utf8');
|
||||
expect(tsx).toContain('<h1>vr</h1>');
|
||||
});
|
||||
|
||||
test('rejects invalid name with INVALID_NAME', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'foo bar', template: 'static', rootDir: root });
|
||||
expect(result).toEqual(expect.objectContaining({ ok: false, code: 'INVALID_NAME' }));
|
||||
});
|
||||
|
||||
test('rejects unknown template with UNKNOWN_TEMPLATE', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'foo', template: 'made-up' as any, rootDir: root });
|
||||
expect(result).toEqual(expect.objectContaining({ ok: false, code: 'UNKNOWN_TEMPLATE' }));
|
||||
});
|
||||
|
||||
test('rejects empty rootDir with NO_ROOT_DIR', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'foo', template: 'static', rootDir: '' });
|
||||
expect(result).toEqual(expect.objectContaining({ ok: false, code: 'NO_ROOT_DIR' }));
|
||||
});
|
||||
|
||||
test('rejects relative rootDir with ROOT_NOT_ABSOLUTE', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'foo', template: 'static', rootDir: 'relative/path' });
|
||||
expect(result).toEqual(expect.objectContaining({ ok: false, code: 'ROOT_NOT_ABSOLUTE' }));
|
||||
});
|
||||
|
||||
test('rejects when target already exists with ALREADY_EXISTS', async () => {
|
||||
const r1 = await scaffolder.scaffold({ name: 'twice', template: 'static', rootDir: root });
|
||||
expect(r1.ok).toBe(true);
|
||||
const r2 = await scaffolder.scaffold({ name: 'twice', template: 'static', rootDir: root });
|
||||
expect(r2).toEqual(expect.objectContaining({ ok: false, code: 'ALREADY_EXISTS' }));
|
||||
});
|
||||
|
||||
test('reports filesWritten for downstream UI', async () => {
|
||||
const result = await scaffolder.scaffold({ name: 'count', template: 'vite-react', rootDir: root });
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.filesWritten.length).toBeGreaterThanOrEqual(5);
|
||||
for (const file of result.filesWritten) {
|
||||
expect(fs.existsSync(file)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user