OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / gdscript.test.js

99 lines · javascript ref:
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')
})