Files
OpenNoodl/packages/noodl-editor/tests/io/ProjectFormatDetector.test.ts
dishant-kumar-thakur 25ce812497 fix(tests): convert io tests to Jasmine matchers for Electron test runner
- Replace toHaveLength(n) with .length).toBe(n) throughout
- Remove require() call for legacyNameToPath (use ES import)
- Remove Node.js fs/os/path integration tests from FormatDetector
  (Electron renderer context doesn't support direct Node.js imports)
- All 3 test files now compile cleanly in webpack test-ci build
- Remaining 58 errors are pre-existing (Git.pull TS2339 x13,
  @noodl-viewer-cloud/execution-history TS2307 x31)
2026-02-19 01:36:12 +05:30

238 lines
9.0 KiB
TypeScript

/**
* ProjectFormatDetector Tests -- STRUCT-004
*
* Uses Jasmine matchers (Electron test runner).
* Integration tests using real filesystem are excluded here
* (Electron renderer context — use createNodeDetector() in Node.js scripts).
*/
import {
ProjectFormatDetector,
DetectorFilesystem,
V2_INDICATORS,
LEGACY_INDICATORS
} from '../../src/editor/src/io/ProjectFormatDetector';
// ---- Mock filesystem factory ------------------------------------------------
function makeMockFs(existingPaths: string[]): DetectorFilesystem {
const set = new Set(existingPaths.map((p) => p.replace(/\\/g, '/')));
return {
exists: (p: string) => set.has(p.replace(/\\/g, '/')),
join: (...parts: string[]) => parts.join('/').replace(/\/+/g, '/')
};
}
// ---- detect() ---------------------------------------------------------------
describe('ProjectFormatDetector.detect()', () => {
it('returns v2/high when nodegx.project.json AND _registry.json exist', async () => {
const mockFs = makeMockFs([
'/proj/nodegx.project.json',
'/proj/components/_registry.json',
'/proj/components'
]);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('v2');
expect(result.confidence).toBe('high');
});
it('returns v2/medium when only nodegx.project.json exists', async () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('v2');
expect(result.confidence).toBe('medium');
});
it('returns v2/medium when only _registry.json exists', async () => {
const mockFs = makeMockFs(['/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('v2');
expect(result.confidence).toBe('medium');
});
it('returns legacy/high when only project.json exists', async () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('legacy');
expect(result.confidence).toBe('high');
});
it('returns unknown/low when no project files exist', async () => {
const mockFs = makeMockFs([]);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('unknown');
expect(result.confidence).toBe('low');
});
it('returns v2 when both project.json and nodegx.project.json exist (mixed)', async () => {
const mockFs = makeMockFs([
'/proj/project.json',
'/proj/nodegx.project.json',
'/proj/components/_registry.json'
]);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('v2');
});
it('includes indicator for nodegx.project.json when found', async () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json', '/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.indicators.some((i) => i.includes('nodegx.project.json'))).toBe(true);
});
it('includes indicator for project.json when found', async () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.indicators.some((i) => i.includes('project.json'))).toBe(true);
});
it('includes indicator for _registry.json when found', async () => {
const mockFs = makeMockFs(['/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.indicators.some((i) => i.includes('_registry.json'))).toBe(true);
});
it('includes "No project files found" indicator when nothing exists', async () => {
const mockFs = makeMockFs([]);
const detector = new ProjectFormatDetector(mockFs);
const result = await detector.detect('/proj');
expect(result.indicators.some((i) => i.toLowerCase().includes('no project files'))).toBe(true);
});
it('handles async filesystem.exists()', async () => {
const asyncFs: DetectorFilesystem = {
exists: async (p: string) => p.includes('nodegx.project.json') || p.includes('_registry.json'),
join: (...parts: string[]) => parts.join('/')
};
const detector = new ProjectFormatDetector(asyncFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('v2');
expect(result.confidence).toBe('high');
});
it('handles filesystem.exists() throwing (treats as not found)', async () => {
const throwingFs: DetectorFilesystem = {
exists: () => { throw new Error('permission denied'); },
join: (...parts: string[]) => parts.join('/')
};
const detector = new ProjectFormatDetector(throwingFs);
const result = await detector.detect('/proj');
expect(result.format).toBe('unknown');
});
});
// ---- detectSync() -----------------------------------------------------------
describe('ProjectFormatDetector.detectSync()', () => {
it('returns v2/high for v2 project', () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json', '/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = detector.detectSync('/proj');
expect(result.format).toBe('v2');
expect(result.confidence).toBe('high');
});
it('returns legacy/high for legacy project', () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
const result = detector.detectSync('/proj');
expect(result.format).toBe('legacy');
expect(result.confidence).toBe('high');
});
it('returns unknown for empty directory', () => {
const mockFs = makeMockFs([]);
const detector = new ProjectFormatDetector(mockFs);
const result = detector.detectSync('/proj');
expect(result.format).toBe('unknown');
});
it('throws when filesystem.exists() returns a Promise', () => {
const asyncFs: DetectorFilesystem = {
exists: async () => false,
join: (...parts: string[]) => parts.join('/')
};
const detector = new ProjectFormatDetector(asyncFs);
let threw = false;
try {
detector.detectSync('/proj');
} catch (e) {
threw = true;
expect((e as Error).message).toContain('synchronous');
}
expect(threw).toBe(true);
});
});
// ---- getFormat() / isV2() / isLegacy() --------------------------------------
describe('ProjectFormatDetector convenience methods', () => {
it('getFormat() returns "v2" for v2 project', async () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json', '/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.getFormat('/proj')).toBe('v2');
});
it('getFormat() returns "legacy" for legacy project', async () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.getFormat('/proj')).toBe('legacy');
});
it('getFormat() returns "unknown" for empty dir', async () => {
const mockFs = makeMockFs([]);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.getFormat('/proj')).toBe('unknown');
});
it('isV2() returns true for v2 project', async () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json', '/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.isV2('/proj')).toBe(true);
});
it('isV2() returns false for legacy project', async () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.isV2('/proj')).toBe(false);
});
it('isLegacy() returns true for legacy project', async () => {
const mockFs = makeMockFs(['/proj/project.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.isLegacy('/proj')).toBe(true);
});
it('isLegacy() returns false for v2 project', async () => {
const mockFs = makeMockFs(['/proj/nodegx.project.json', '/proj/components/_registry.json']);
const detector = new ProjectFormatDetector(mockFs);
expect(await detector.isLegacy('/proj')).toBe(false);
});
});
// ---- V2_INDICATORS / LEGACY_INDICATORS constants ----------------------------
describe('Sentinel constants', () => {
it('V2_INDICATORS.projectFile is nodegx.project.json', () => {
expect(V2_INDICATORS.projectFile).toBe('nodegx.project.json');
});
it('V2_INDICATORS.registryFile is components/_registry.json', () => {
expect(V2_INDICATORS.registryFile).toBe('components/_registry.json');
});
it('LEGACY_INDICATORS.projectFile is project.json', () => {
expect(LEGACY_INDICATORS.projectFile).toBe('project.json');
});
});