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: string) => { 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: string, index: number) => { 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); }); });