import { test } from 'brittle'
import { parseFile } from '../lib/parsers/gdscript.js'
test('gdscript: class_name + extends + methods + signals', (t) => {
const src = `class_name KeetFeed
extends Node
signal room_selected(room_id: String)
signal message_received(data: Dictionary)
@export var max_messages: int = 100
const MAX_CACHED := 100
func _ready() -> void:
pass
func send_message(text: String, room_id: String) -> bool:
return false
static func create_from_config(config: Dictionary) -> KeetFeed:
return null
`
const result = parseFile(src, 'keet_feed.gd')
t.is(result.error, undefined, 'no parse error')
t.is(result.classes.length, 1, '1 class')
const cls = result.classes[0]
t.is(cls.name, 'KeetFeed', 'class_name becomes class name')
t.is(cls.extends, 'Node', 'extends parsed')
t.is(cls.methods.length, 2, '2 instance methods')
const methodNames = cls.methods.map(m => m.name)
t.ok(methodNames.includes('_ready'), '_ready method')
t.ok(methodNames.includes('send_message'), 'send_message method')
t.is(cls.staticMethods.length, 1, '1 static method')
t.is(cls.staticMethods[0].name, 'create_from_config', 'static method name')
t.is(result.signals.length, 2, '2 signals')
t.is(result.signals[0].name, 'room_selected', 'first signal')
t.is(result.exports.length, 1, '1 export (the class_name)')
t.is(result.exports[0].name, 'KeetFeed', 'export name matches class_name')
t.ok(result.constants.length >= 2, 'has var + const constants')
})
test('gdscript: preload() extraction', (t) => {
const src = `class_name Game
extends Node
const PlayerScene := preload("res://scenes/player/player.tscn")
const EnemyScene: PackedScene = preload("res://scenes/enemies/enemy.tscn")
func _ready() -> void:
pass
`
const result = parseFile(src, 'game.gd')
t.is(result.imports.length, 2, '2 preload imports')
t.is(result.imports[0].specifier, 'res://scenes/player/player.tscn', 'first preload path')
t.is(result.imports[1].specifier, 'res://scenes/enemies/enemy.tscn', 'second preload path')
})
test('gdscript: handles file without class_name', (t) => {
const src = `extends RefCounted
func util_function(x: int) -> int:
return x * 2
`
const result = parseFile(src, 'util.gd')
t.is(result.error, undefined, 'no parse error')
// Class name should default to filename (without .gd)
if (result.classes.length > 0) {
t.is(result.classes[0].name, 'util', 'class name defaults to filename')
}
})
test('gdscript: constructor (_init)', (t) => {
const src = `class_name MyClass
extends Node
func _init(name: String) -> void:
pass
func do_thing():
pass
`
const result = parseFile(src, 'my_class.gd')
const cls = result.classes[0]
t.ok(cls.constructor, 'has constructor')
t.is(cls.constructor.params.length, 1, 'constructor param count')
t.is(cls.methods.length, 1, '_init is not counted as a regular method')
})