mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-22 10:05:37 -04:00
6bb56c6e5b
- "For Each Child Variable" now also properly support choosing local variables for the variable to iterate on, and for the variable where to store the child and the optional variable where to store the name. - The loop counter starts from 0 and is automatically increased by 1 each time the event is repeated.
535 lines
17 KiB
JavaScript
535 lines
17 KiB
JavaScript
const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js');
|
|
const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks');
|
|
const {
|
|
generateCompiledEventsForEventsFunction,
|
|
generateCompiledEventsFromSerializedEvents,
|
|
} = require('../TestUtils/CodeGenerationHelpers.js');
|
|
|
|
/**
|
|
* Helper generating an event, ready to be unserialized, adding 1 to
|
|
* "TestVariable" of the specified object (or object group).
|
|
*/
|
|
const makeAddOneToObjectTestVariableEvent = (objectName) => ({
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarObjet' },
|
|
parameters: [objectName, 'TestVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
});
|
|
|
|
describe('libGD.js - GDJS Code Generation integration tests', function () {
|
|
let gd = null;
|
|
beforeAll(async () => {
|
|
gd = await initializeGDevelopJs();
|
|
});
|
|
|
|
it('does not crash when generating code with diagnostics', function () {
|
|
const serializerElement = gd.Serializer.fromJSObject([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: [
|
|
'Counter',
|
|
'+',
|
|
'1 + VariableThatDoesNotExist.Test.Test2',
|
|
],
|
|
},
|
|
{
|
|
type: { value: 'SetNumberObjectVariable' },
|
|
parameters: [
|
|
'NonExistingObject',
|
|
'NonExistingVariable',
|
|
'=',
|
|
'123',
|
|
],
|
|
},
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['Counter', '+', '3'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
]);
|
|
|
|
const runCompiledEvents = generateCompiledEventsFromSerializedEvents(
|
|
gd,
|
|
serializerElement
|
|
);
|
|
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
|
|
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
|
|
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(3);
|
|
});
|
|
|
|
it('does not generate anything for a disabled event and its sub events', function () {
|
|
const serializerElement = gd.Serializer.fromJSObject([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['Counter', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
disabled: true,
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['Counter', '+', '1'],
|
|
},
|
|
],
|
|
events: [
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['Counter', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
const runCompiledEvents = generateCompiledEventsFromSerializedEvents(
|
|
gd,
|
|
serializerElement
|
|
);
|
|
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
|
|
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
|
|
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(1);
|
|
});
|
|
|
|
it('generates a working function creating objects', function () {
|
|
const eventsSerializerElement = gd.Serializer.fromJSObject([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'Create' },
|
|
parameters: ['', 'MyObjectA', '0', '0', ''],
|
|
},
|
|
],
|
|
events: [makeAddOneToObjectTestVariableEvent('MyObjectA')],
|
|
},
|
|
makeAddOneToObjectTestVariableEvent('MyObjectA'),
|
|
makeAddOneToObjectTestVariableEvent('MyObjectB'),
|
|
]);
|
|
|
|
const project = new gd.ProjectHelper.createNewGDJSProject();
|
|
const eventsFunction = new gd.EventsFunction();
|
|
eventsFunction
|
|
.getEvents()
|
|
.unserializeFrom(project, eventsSerializerElement);
|
|
|
|
// Add an object parameter to the function:
|
|
eventsFunction
|
|
.getParameters()
|
|
.insertNewParameter('MyObjectA', 0)
|
|
.setType('object');
|
|
eventsFunction
|
|
.getParameters()
|
|
.insertNewParameter('MyObjectB', 1)
|
|
.setType('object');
|
|
|
|
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
const emptyMyObjectALists = gdjs.Hashtable.newFrom({ MyObjectA: [] });
|
|
const myObjectB = runtimeScene.createObject('MyObjectB');
|
|
const myObjectBLists = gdjs.Hashtable.newFrom({ MyObjectB: [myObjectB] });
|
|
|
|
runCompiledEvents(gdjs, runtimeScene, [
|
|
emptyMyObjectALists,
|
|
myObjectBLists,
|
|
]);
|
|
|
|
// Check that the object was created and its variable was incremented twice.
|
|
expect(runtimeScene.getObjects('MyObjectA')).toHaveLength(1);
|
|
const firstMyObjectA = runtimeScene.getObjects('MyObjectA')[0];
|
|
expect(firstMyObjectA.getVariables().has('TestVariable')).toBe(true);
|
|
expect(
|
|
firstMyObjectA.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
// Object B has nothing to do with the "Create", its variable is just incremented once.
|
|
expect(myObjectB.getVariables().get('TestVariable').getAsNumber()).toBe(1);
|
|
|
|
// Check that if the function is called with the first MyObjectA as parameter, it won't change
|
|
// the way a new object is created...
|
|
const myObjectALists = gdjs.Hashtable.newFrom({
|
|
MyObjectA: [firstMyObjectA],
|
|
});
|
|
runCompiledEvents(gdjs, runtimeScene, [myObjectALists, myObjectBLists]);
|
|
expect(runtimeScene.getObjects('MyObjectA')).toHaveLength(2);
|
|
const secondMyObjectA = runtimeScene.getObjects('MyObjectA')[1];
|
|
expect(secondMyObjectA.getVariables().has('TestVariable')).toBe(true);
|
|
expect(
|
|
secondMyObjectA.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
// ... and the first, "already created" MyObjectA is only picked by the second event.
|
|
// The first event with the "Create" will only pick the "just created" object.
|
|
expect(
|
|
firstMyObjectA.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(3);
|
|
|
|
// Object B has nothing to do with the "Create", its variable is just incremented again.
|
|
expect(myObjectB.getVariables().get('TestVariable').getAsNumber()).toBe(2);
|
|
|
|
eventsFunction.delete();
|
|
project.delete();
|
|
});
|
|
|
|
it('generates working functions with groups', function () {
|
|
// Create events that increment twice the variable of a group, and
|
|
// only once for the 3rd parameter.
|
|
const eventsSerializerElement = gd.Serializer.fromJSObject([
|
|
makeAddOneToObjectTestVariableEvent('MyObjectGroup'),
|
|
makeAddOneToObjectTestVariableEvent('MyObjectGroup'),
|
|
makeAddOneToObjectTestVariableEvent('ObjectParam3'),
|
|
]);
|
|
|
|
const project = new gd.ProjectHelper.createNewGDJSProject();
|
|
const eventsFunction = new gd.EventsFunction();
|
|
eventsFunction
|
|
.getEvents()
|
|
.unserializeFrom(project, eventsSerializerElement);
|
|
const group = eventsFunction
|
|
.getObjectGroups()
|
|
.insertNew('MyObjectGroup', 0);
|
|
group.setName('MyObjectGroup');
|
|
group.addObject('ObjectParam1');
|
|
group.addObject('ObjectParam2');
|
|
|
|
eventsFunction
|
|
.getParameters()
|
|
.insertNewParameter('ObjectParam1', 0)
|
|
.setType('object');
|
|
eventsFunction
|
|
.getParameters()
|
|
.insertNewParameter('ObjectParam2', 1)
|
|
.setType('object');
|
|
eventsFunction
|
|
.getParameters()
|
|
.insertNewParameter('ObjectParam3', 2)
|
|
.setType('object');
|
|
|
|
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
|
|
// Check that when running the function, the group works in the function.
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
const myObjectA = runtimeScene.createObject('MyObjectA');
|
|
const myObjectB = runtimeScene.createObject('MyObjectB');
|
|
const myObjectC = runtimeScene.createObject('MyObjectC');
|
|
const myObjectD = runtimeScene.createObject('MyObjectD');
|
|
const objectParam1Lists = gdjs.Hashtable.newFrom({
|
|
// The first parameter is itself a collection of multiple objects
|
|
// with different names.
|
|
MyObjectA: [myObjectA],
|
|
MyObjectB: [myObjectB],
|
|
});
|
|
const objectParam2Lists = gdjs.Hashtable.newFrom({
|
|
MyObjectC: [myObjectC],
|
|
});
|
|
const objectParam3Lists = gdjs.Hashtable.newFrom({
|
|
MyObjectD: [myObjectD],
|
|
});
|
|
|
|
runCompiledEvents(gdjs, runtimeScene, [
|
|
objectParam1Lists,
|
|
objectParam2Lists,
|
|
objectParam3Lists,
|
|
]);
|
|
|
|
expect(runtimeScene.getObjects('MyObjectA')).toHaveLength(1);
|
|
const firstMyObjectA = runtimeScene.getObjects('MyObjectA')[0];
|
|
expect(
|
|
firstMyObjectA.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
expect(runtimeScene.getObjects('MyObjectB')).toHaveLength(1);
|
|
const firstMyObjectB = runtimeScene.getObjects('MyObjectB')[0];
|
|
expect(
|
|
firstMyObjectB.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
expect(runtimeScene.getObjects('MyObjectC')).toHaveLength(1);
|
|
const firstMyObjectC = runtimeScene.getObjects('MyObjectC')[0];
|
|
expect(
|
|
firstMyObjectC.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
expect(runtimeScene.getObjects('MyObjectD')).toHaveLength(1);
|
|
const firstMyObjectD = runtimeScene.getObjects('MyObjectD')[0];
|
|
expect(
|
|
firstMyObjectD.getVariables().get('TestVariable').getAsNumber()
|
|
).toBe(1);
|
|
|
|
eventsFunction.delete();
|
|
project.delete();
|
|
});
|
|
|
|
it('generates a working function with BuiltinCommonInstructions::Once', function () {
|
|
// Event to create an object, then add
|
|
const eventsSerializerElement = gd.Serializer.fromJSObject([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [
|
|
{
|
|
type: {
|
|
value: 'BuiltinCommonInstructions::Once',
|
|
},
|
|
parameters: [],
|
|
},
|
|
],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['SuccessVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [
|
|
{
|
|
type: {
|
|
value: 'BuiltinCommonInstructions::Once',
|
|
},
|
|
parameters: [],
|
|
},
|
|
],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['SuccessVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
]);
|
|
|
|
const project = new gd.ProjectHelper.createNewGDJSProject();
|
|
const eventsFunction = new gd.EventsFunction();
|
|
eventsFunction
|
|
.getEvents()
|
|
.unserializeFrom(project, eventsSerializerElement);
|
|
|
|
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
|
|
// Check that after running the compiled events twice, actions were
|
|
// executed only twice.
|
|
expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
eventsFunction.delete();
|
|
project.delete();
|
|
});
|
|
|
|
it('generates a working function with BuiltinCommonInstructions::Once with stable IDs across compilation', function () {
|
|
// Create events using the Trigger Once condition.
|
|
const eventsSerializerElement = gd.Serializer.fromJSON(
|
|
JSON.stringify([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [
|
|
{
|
|
type: {
|
|
value: 'BuiltinCommonInstructions::Once',
|
|
},
|
|
parameters: [],
|
|
},
|
|
],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['SuccessVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [
|
|
{
|
|
type: {
|
|
value: 'BuiltinCommonInstructions::Once',
|
|
},
|
|
parameters: [],
|
|
},
|
|
],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['SuccessVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
])
|
|
);
|
|
|
|
const project = new gd.ProjectHelper.createNewGDJSProject();
|
|
const eventsFunction = new gd.EventsFunction();
|
|
eventsFunction
|
|
.getEvents()
|
|
.unserializeFrom(project, eventsSerializerElement);
|
|
|
|
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
|
|
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents(gdjs, runtimeScene, []);
|
|
|
|
// Check that after running the compiled events twice, actions were
|
|
// executed only twice.
|
|
expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
// Check that compiling again the events will result in stable ids for Trigger Once conditions
|
|
// (trigger once should not be launched again), even after a copy of the events function.
|
|
const eventsFunctionCopy = eventsFunction.clone();
|
|
const runCompiledEvents2 = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunctionCopy
|
|
);
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents2(gdjs, runtimeScene, []);
|
|
runtimeScene.getOnceTriggers().startNewFrame();
|
|
runCompiledEvents2(gdjs, runtimeScene, []);
|
|
|
|
expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
eventsFunction.delete();
|
|
eventsFunctionCopy.delete();
|
|
project.delete();
|
|
});
|
|
|
|
it('generates a lifecycle function that registers itself, and unregister itself if hot-reloaded', function () {
|
|
// Event to create an object, then add
|
|
const eventsSerializerElement = gd.Serializer.fromJSObject([
|
|
{
|
|
type: 'BuiltinCommonInstructions::Standard',
|
|
conditions: [],
|
|
actions: [
|
|
{
|
|
type: { value: 'ModVarScene' },
|
|
parameters: ['SuccessVariable', '+', '1'],
|
|
},
|
|
],
|
|
events: [],
|
|
},
|
|
]);
|
|
|
|
const project = new gd.ProjectHelper.createNewGDJSProject();
|
|
const eventsFunction = new gd.EventsFunction();
|
|
eventsFunction.setName('onScenePreEvents');
|
|
eventsFunction
|
|
.getEvents()
|
|
.unserializeFrom(project, eventsSerializerElement);
|
|
|
|
const runCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
|
|
const { gdjs, runtimeScene, mocks } = makeMinimalGDJSMock();
|
|
runCompiledEvents(
|
|
gdjs,
|
|
runtimeScene /*, Don't pass arguments to not run the function. */
|
|
);
|
|
mocks.runRuntimeScenePreEventsCallbacks();
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(1);
|
|
mocks.runRuntimeScenePreEventsCallbacks();
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(2);
|
|
|
|
// Simulate a hot reloading by recompiling the function and running it again.
|
|
const runHotReloadedCompiledEvents = generateCompiledEventsForEventsFunction(
|
|
gd,
|
|
project,
|
|
eventsFunction
|
|
);
|
|
runHotReloadedCompiledEvents(
|
|
gdjs,
|
|
runtimeScene /*, Don't pass arguments to not run the function. */
|
|
);
|
|
|
|
// Ensure that when we call the callbacks, it's called only once (not registered twice).
|
|
mocks.runRuntimeScenePreEventsCallbacks();
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(3);
|
|
mocks.runRuntimeScenePreEventsCallbacks();
|
|
expect(
|
|
runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
|
|
).toBe(4);
|
|
|
|
eventsFunction.delete();
|
|
project.delete();
|
|
});
|
|
});
|