/**
* GodotPluginBridge Tests
* ISO/IEC 29119 compliant test structure
*
* Note: Tests requiring WebSocket mocking are marked as .skip
* since Jest ES module mocking has limitations.
* Full integration tests require the Godot plugin running.
*/
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import {
GodotPluginBridge,
getGodotPluginBridge,
resetGodotPluginBridge,
} from './GodotPluginBridge.js';
import { CircuitState } from '../core/CircuitBreaker.js';
describe('GodotPluginBridge', () => {
let bridge: GodotPluginBridge;
beforeEach(() => {
resetGodotPluginBridge();
bridge = new GodotPluginBridge({
host: '127.0.0.1',
port: 6505,
reconnectInterval: 100,
maxReconnectAttempts: 3,
requestTimeout: 1000,
});
});
afterEach(() => {
bridge.disconnect();
});
describe('initialization', () => {
it('should create instance with default config', () => {
const defaultBridge = new GodotPluginBridge();
expect(defaultBridge).toBeDefined();
defaultBridge.disconnect();
});
it('should create instance with custom config', () => {
const customBridge = new GodotPluginBridge({
host: 'localhost',
port: 9999,
reconnectInterval: 500,
maxReconnectAttempts: 10,
requestTimeout: 5000,
});
expect(customBridge).toBeDefined();
customBridge.disconnect();
});
it('should not be connected initially', () => {
expect(bridge.isPluginConnected()).toBe(false);
});
it('should have closed circuit breaker initially', () => {
expect(bridge.getCircuitState()).toBe(CircuitState.CLOSED);
});
it('should report disconnected status', () => {
const status = bridge.getStatus();
expect(status.connected).toBe(false);
expect(status.circuitState).toBe('closed');
expect(status.reconnectAttempts).toBe(0);
});
});
describe('disconnected state', () => {
it('should throw if send is called when not connected', async () => {
await expect(bridge.send('echo', {})).rejects.toThrow('Not connected');
});
it('should throw if sendOrThrow is called when not connected', async () => {
await expect(bridge.sendOrThrow('echo', {})).rejects.toThrow('Not connected');
});
it('should return false for isPluginConnected when not connected', () => {
expect(bridge.isPluginConnected()).toBe(false);
});
it('should handle disconnect when not connected', () => {
// Should not throw
bridge.disconnect();
expect(bridge.isPluginConnected()).toBe(false);
});
});
describe('circuit breaker', () => {
it('should allow resetting circuit breaker', () => {
bridge.resetCircuitBreaker();
expect(bridge.getCircuitState()).toBe(CircuitState.CLOSED);
});
it('should report circuit state in status', () => {
const status = bridge.getStatus();
expect(['closed', 'open', 'half-open']).toContain(status.circuitState);
});
});
describe('tryConnect', () => {
// Skip this test - requires network and has timeout issues
// Full integration test in tests/integration/bridge-integration.test.ts
it.skip('should return false when connection fails (no server)', async () => {
// No server running, should fail
const result = await bridge.tryConnect();
expect(result).toBe(false);
});
});
describe('singleton', () => {
it('should return same instance from getGodotPluginBridge', () => {
resetGodotPluginBridge();
const instance1 = getGodotPluginBridge();
const instance2 = getGodotPluginBridge();
expect(instance1).toBe(instance2);
});
it('should create new instance after reset', () => {
const instance1 = getGodotPluginBridge();
resetGodotPluginBridge();
const instance2 = getGodotPluginBridge();
expect(instance1).not.toBe(instance2);
});
});
describe('high-level API types', () => {
// These tests verify the API exists, not that it works
// (requires actual plugin connection for full tests)
it('should have echo method', () => {
expect(typeof bridge.echo).toBe('function');
});
it('should have createScene method', () => {
expect(typeof bridge.createScene).toBe('function');
});
it('should have openScene method', () => {
expect(typeof bridge.openScene).toBe('function');
});
it('should have saveScene method', () => {
expect(typeof bridge.saveScene).toBe('function');
});
it('should have getCurrentScene method', () => {
expect(typeof bridge.getCurrentScene).toBe('function');
});
it('should have getSceneTree method', () => {
expect(typeof bridge.getSceneTree).toBe('function');
});
it('should have addNode method', () => {
expect(typeof bridge.addNode).toBe('function');
});
it('should have editNode method', () => {
expect(typeof bridge.editNode).toBe('function');
});
it('should have removeNode method', () => {
expect(typeof bridge.removeNode).toBe('function');
});
it('should have getNodeProperties method', () => {
expect(typeof bridge.getNodeProperties).toBe('function');
});
it('should have writeScript method', () => {
expect(typeof bridge.writeScript).toBe('function');
});
it('should have attachScript method', () => {
expect(typeof bridge.attachScript).toBe('function');
});
it('should have getScriptContent method', () => {
expect(typeof bridge.getScriptContent).toBe('function');
});
it('should have runProject method', () => {
expect(typeof bridge.runProject).toBe('function');
});
it('should have stopProject method', () => {
expect(typeof bridge.stopProject).toBe('function');
});
it('should have getProjectInfo method', () => {
expect(typeof bridge.getProjectInfo).toBe('function');
});
});
describe('event emitter', () => {
it('should extend EventEmitter', () => {
expect(typeof bridge.on).toBe('function');
expect(typeof bridge.emit).toBe('function');
expect(typeof bridge.off).toBe('function');
});
it('should allow registering event listeners', () => {
const listener = (): void => {};
bridge.on('connected', listener);
bridge.off('connected', listener);
});
});
});