mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-21 01:25:32 -04:00
Add integration test for objects created via a behavior action parameter (#8576)
Verify that a behavior's custom action whose events Create the object passed as parameter (FireBullet-style) leads to the newly created instances being picked in the calling scope, while a previously created instance of the same object remains untouched. Don't show in changelog
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js');
|
||||
const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks');
|
||||
const {
|
||||
generateCompiledEventsForEventsBasedBehavior,
|
||||
generateCompiledEventsForLayout,
|
||||
} = require('../TestUtils/CodeGenerationHelpers.js');
|
||||
|
||||
describe('libGD.js - GDJS Advanced Behavior Code Generation integration tests', function () {
|
||||
let gd = null;
|
||||
beforeAll(async () => {
|
||||
gd = await initializeGDevelopJs();
|
||||
});
|
||||
|
||||
describe('Object parameter creation (FireBullet-like behavior)', () => {
|
||||
it('Picks only the instances created by a behavior action via an object parameter', () => {
|
||||
// A behavior MyBehavior has a custom action MyAction that takes an
|
||||
// object as parameter. The action's events use Create twice on the
|
||||
// object passed as parameter, mimicking a FireBullet behavior.
|
||||
//
|
||||
// The scene contains two objects: Bullet (the parameter type) and
|
||||
// Cannon (the object holding the behavior). The scene events first
|
||||
// create one Bullet directly (without going through the behavior),
|
||||
// and in a subsequent event call MyBehavior::MyAction(Bullet) on the
|
||||
// Cannon, then increment a Counter on the picked Bullet. Only the
|
||||
// two newly created Bullets must be picked by this increment - not
|
||||
// the one created in the previous scene event.
|
||||
const project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
0
|
||||
);
|
||||
const eventsBasedBehavior = eventsFunctionsExtension
|
||||
.getEventsBasedBehaviors()
|
||||
.insertNew('MyBehavior', 0);
|
||||
|
||||
// Behavior's MyAction creates the parameter object twice.
|
||||
const behaviorEventsSerializerElement = gd.Serializer.fromJSObject([
|
||||
{
|
||||
type: 'BuiltinCommonInstructions::Standard',
|
||||
conditions: [],
|
||||
actions: [
|
||||
{
|
||||
type: { value: 'Create' },
|
||||
parameters: ['', 'MyObjectParam', '0', '0', ''],
|
||||
},
|
||||
{
|
||||
type: { value: 'Create' },
|
||||
parameters: ['', 'MyObjectParam', '0', '0', ''],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const behaviorEventsFunction = eventsBasedBehavior
|
||||
.getEventsFunctions()
|
||||
.insertNewEventsFunction('MyAction', 0);
|
||||
behaviorEventsFunction
|
||||
.getEvents()
|
||||
.unserializeFrom(project, behaviorEventsSerializerElement);
|
||||
behaviorEventsSerializerElement.delete();
|
||||
gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters(
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior
|
||||
);
|
||||
// Add the object parameter used in the behavior's events. Use
|
||||
// "objectListOrEmptyIfJustDeclared" (the IDE's "Created objects"
|
||||
// parameter type) so the parameter starts empty in the caller scope
|
||||
// and only contains the instances created inside the action.
|
||||
const objectParameter = behaviorEventsFunction
|
||||
.getParameters()
|
||||
.insertNewParameter(
|
||||
'MyObjectParam',
|
||||
behaviorEventsFunction.getParameters().getParametersCount()
|
||||
);
|
||||
objectParameter.setType('objectListOrEmptyIfJustDeclared');
|
||||
|
||||
// Scene with the two objects:
|
||||
// - Bullet, the object passed as parameter
|
||||
// - Cannon, the object holding the behavior
|
||||
const layout = project.insertNewLayout('MyScene', 0);
|
||||
layout.getObjects().insertNewObject(project, 'Sprite', 'Bullet', 0);
|
||||
const cannon = layout
|
||||
.getObjects()
|
||||
.insertNewObject(project, 'Sprite', 'Cannon', 1);
|
||||
// Register the events-based behavior on the platform so that the
|
||||
// layout code generator can find the metadata for the action call.
|
||||
const platformExtension = new gd.PlatformExtension();
|
||||
gd.MetadataDeclarationHelper.declareExtension(
|
||||
platformExtension,
|
||||
eventsFunctionsExtension
|
||||
);
|
||||
const behaviorMethodMangledNames = new gd.MapStringString();
|
||||
gd.MetadataDeclarationHelper.generateBehaviorMetadata(
|
||||
project,
|
||||
platformExtension,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior,
|
||||
behaviorMethodMangledNames
|
||||
);
|
||||
behaviorMethodMangledNames.delete();
|
||||
gd.JsPlatform.get().addNewExtension(platformExtension);
|
||||
platformExtension.delete();
|
||||
|
||||
cannon.addNewBehavior(
|
||||
project,
|
||||
'MyExtension::MyBehavior',
|
||||
'MyBehavior'
|
||||
);
|
||||
|
||||
// Scene events:
|
||||
// - First event: create one Bullet directly.
|
||||
// - Second event: call the behavior's MyAction passing Bullet, then
|
||||
// increment Counter on the picked Bullets.
|
||||
const layoutEventsSerializerElement = gd.Serializer.fromJSObject([
|
||||
{
|
||||
type: 'BuiltinCommonInstructions::Standard',
|
||||
conditions: [],
|
||||
actions: [
|
||||
{
|
||||
type: { value: 'Create' },
|
||||
parameters: ['', 'Bullet', '0', '0', ''],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'BuiltinCommonInstructions::Standard',
|
||||
conditions: [],
|
||||
actions: [
|
||||
{
|
||||
type: { value: 'MyExtension::MyBehavior::MyAction' },
|
||||
parameters: ['Cannon', 'MyBehavior', 'Bullet'],
|
||||
},
|
||||
{
|
||||
type: { value: 'ModVarObjet' },
|
||||
parameters: ['Bullet', 'Counter', '+', '1'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
layout
|
||||
.getEvents()
|
||||
.unserializeFrom(project, layoutEventsSerializerElement);
|
||||
layoutEventsSerializerElement.delete();
|
||||
|
||||
// Serialize before we delete the project, since the mock needs the
|
||||
// project/scene data to instantiate the runtime scene.
|
||||
const serializedProjectElement = new gd.SerializerElement();
|
||||
project.serializeTo(serializedProjectElement);
|
||||
const serializedSceneElement = new gd.SerializerElement();
|
||||
layout.serializeTo(serializedSceneElement);
|
||||
|
||||
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
|
||||
gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)),
|
||||
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
|
||||
});
|
||||
|
||||
// Compile the behavior - this also registers it on the gdjs mock.
|
||||
const CompiledRuntimeBehavior =
|
||||
generateCompiledEventsForEventsBasedBehavior(
|
||||
gd,
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior,
|
||||
gdjs,
|
||||
{ logCode: false }
|
||||
);
|
||||
|
||||
// Compile the layout events.
|
||||
const runCompiledLayoutEvents = generateCompiledEventsForLayout(
|
||||
gd,
|
||||
project,
|
||||
layout,
|
||||
false
|
||||
);
|
||||
|
||||
serializedProjectElement.delete();
|
||||
serializedSceneElement.delete();
|
||||
project.delete();
|
||||
|
||||
// Manually create a Cannon instance with MyBehavior attached: the
|
||||
// minimal GDJS mock does not auto-instantiate behaviors from the
|
||||
// scene data.
|
||||
const cannonInstance = runtimeScene.createObject('Cannon');
|
||||
const behaviorInstance = new CompiledRuntimeBehavior(
|
||||
runtimeScene,
|
||||
{ name: 'MyBehavior', type: 'MyExtension::MyBehavior' },
|
||||
cannonInstance
|
||||
);
|
||||
cannonInstance.addBehavior(behaviorInstance);
|
||||
|
||||
runCompiledLayoutEvents(gdjs, runtimeScene);
|
||||
|
||||
// Three Bullets should exist on the scene: one created directly in
|
||||
// the first scene event, plus the two created by the behavior's
|
||||
// action via the object parameter.
|
||||
const bulletInstances = runtimeScene.getObjects('Bullet');
|
||||
expect(bulletInstances.length).toBe(3);
|
||||
|
||||
// Only the two Bullets created through the behavior's action must
|
||||
// be picked by the increment that follows the action call. The
|
||||
// Bullet created directly in the previous scene event must keep
|
||||
// its Counter at 0.
|
||||
const counterValues = bulletInstances
|
||||
.map((instance) =>
|
||||
instance.getVariables().get('Counter').getAsNumber()
|
||||
)
|
||||
.sort();
|
||||
expect(counterValues).toEqual([0, 1, 1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user