113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
import { execSync } from 'child_process';
|
|
|
|
/**
|
|
* Electron Build Smoke Test
|
|
*
|
|
* Purpose: Detect browser context errors during Electron build
|
|
*
|
|
* This test catches bundling issues where Node.js modules are imported
|
|
* in the renderer process, causing runtime errors.
|
|
*
|
|
* RED Phase: This test MUST FAIL due to externalized modules
|
|
*/
|
|
|
|
describe('Electron Build Smoke Tests', () => {
|
|
test('should build Electron app without browser context errors', () => {
|
|
// When: Building the Electron companion app
|
|
let buildOutput: string;
|
|
|
|
try {
|
|
buildOutput = execSync('npm run companion:build', {
|
|
cwd: process.cwd(),
|
|
encoding: 'utf-8',
|
|
stdio: 'pipe',
|
|
});
|
|
} catch (error: any) {
|
|
buildOutput = error.stdout + error.stderr;
|
|
}
|
|
|
|
// Then: Build should not contain externalized module warnings
|
|
const foundErrors: string[] = [];
|
|
|
|
// Split output into lines and check each line
|
|
const lines = buildOutput.split('\n');
|
|
lines.forEach(line => {
|
|
if (line.includes('has been externalized for browser compatibility')) {
|
|
foundErrors.push(line.trim());
|
|
}
|
|
if (line.includes('Cannot access') && line.includes('in client code')) {
|
|
foundErrors.push(line.trim());
|
|
}
|
|
});
|
|
|
|
// This WILL FAIL in RED phase due to electron/fs/path being externalized
|
|
expect(
|
|
foundErrors.length,
|
|
`Browser context errors detected during build:\n\n${foundErrors.map((e, i) => `${i + 1}. ${e}`).join('\n')}\n\n` +
|
|
`These indicate Node.js modules (electron, fs, path) are being imported in renderer code.\n` +
|
|
`This will cause runtime errors when the app launches.`
|
|
).toBe(0);
|
|
});
|
|
|
|
test('should not import Node.js modules in renderer source code', () => {
|
|
// Given: Renderer source code
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const rendererPath = path.join(
|
|
process.cwd(),
|
|
'apps/companion/renderer'
|
|
);
|
|
|
|
// When: Checking renderer source for forbidden imports
|
|
const forbiddenPatterns = [
|
|
{ pattern: /from\s+['"]electron['"]/, name: 'electron' },
|
|
{ pattern: /require\(['"]electron['"]\)/, name: 'electron' },
|
|
{ pattern: /from\s+['"]fs['"]/, name: 'fs' },
|
|
{ pattern: /require\(['"]fs['"]\)/, name: 'fs' },
|
|
{ pattern: /from\s+['"]path['"]/, name: 'path' },
|
|
{ pattern: /require\(['"]path['"]\)/, name: 'path' },
|
|
];
|
|
|
|
const violations: Array<{ file: string; line: number; import: string; module: string }> = [];
|
|
|
|
function scanDirectory(dir: string) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
|
|
entries.forEach((entry: any) => {
|
|
const fullPath = path.join(dir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
scanDirectory(fullPath);
|
|
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const lines = content.split('\n');
|
|
|
|
lines.forEach((line, index) => {
|
|
forbiddenPatterns.forEach(({ pattern, name }) => {
|
|
if (pattern.test(line)) {
|
|
violations.push({
|
|
file: path.relative(process.cwd(), fullPath),
|
|
line: index + 1,
|
|
import: line.trim(),
|
|
module: name,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
scanDirectory(rendererPath);
|
|
|
|
// Then: No Node.js modules should be imported in renderer
|
|
expect(
|
|
violations.length,
|
|
`Found Node.js module imports in renderer source code:\n\n${
|
|
violations.map(v => `${v.file}:${v.line}\n Module: ${v.module}\n Code: ${v.import}`).join('\n\n')
|
|
}\n\nRenderer code must use the preload script or IPC to access Node.js APIs.`
|
|
).toBe(0);
|
|
});
|
|
}); |