Files
GDevelop/GDevelop.js/__tests__/GDJSSceneCodeGenerationIntegrationTests.js
Florian Rival a51c223c9c Add experimental, work-in-progress visual editor for custom objects (aka "prefabs"/"templates") (#6699)
* Still to do: 
  * Properly handle effects (disable 3D effects) for layers
  * Handle hot reloading properly
  * Avoid duplicating the configuration of the custom object inside each object created from it. Instead, only store the modified values.
  * Add a "Extract as a custom object ("prefab")" when selecting instances in a scene.
  * Add a dialog to give choice between 2D or 3D object when creating one.
  * Make sure "behavior shared data" are properly handled (physics, pathfinding...)
  * Check if we need to give an expression to translate coordinates from the parent to the local custom object.
  * Ensure a deleted custom object does not break the editor

Co-authored-by: Davy Hélard <davy.helard@gmail.com>
2024-07-11 11:27:34 +02:00

387 lines
13 KiB
JavaScript

const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js');
const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js');
const {
generateCompiledEventsForLayout,
} = require('../TestUtils/CodeGenerationHelpers.js');
describe('libGD.js - GDJS Scene Code Generation integration tests', function () {
let gd = null;
beforeAll(async () => {
gd = await initializeGDevelopJs();
});
it('generates code for a link to an external events', function () {
const project = new gd.ProjectHelper.createNewGDJSProject();
const layout = project.insertNewLayout('Scene', 0);
layout.getVariables().insertNew('Counter', 0).setValue(0);
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: ['Counter', '+', '1'],
},
],
events: [],
},
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: ['Counter', '+', '2'],
},
],
events: [
{
type: 'BuiltinCommonInstructions::Link',
target: 'External events 1',
},
],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const externalEvents = project.insertNewExternalEvents(
'External events 1',
0
);
const serializedExternalEventsEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: ['Counter', '+', '4'],
},
],
events: [],
},
// The new event is disabled, so not executed. And the link
// should not be inserted (so no infinite loop), because it's not
// even processed (as it's inside a disabled event).
{
type: 'BuiltinCommonInstructions::Standard',
disabled: true,
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: ['Counter', '+', '8'],
},
],
events: [
{
type: 'BuiltinCommonInstructions::Link',
target: 'External events 1',
},
],
},
]);
externalEvents
.getEvents()
.unserializeFrom(project, serializedExternalEventsEvents);
const serializedProjectElement = new gd.SerializerElement();
project.serializeTo(serializedProjectElement);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)),
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(7);
project.delete();
});
it('generates code for nested links to external events', function () {
const project = new gd.ProjectHelper.createNewGDJSProject();
const layout = project.insertNewLayout('Scene', 0);
layout.getVariables().insertNew('Counter', 0).setValue(0);
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Link',
target: 'External events 1',
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const externalEvents1 = project.insertNewExternalEvents(
'External events 1',
0
);
const serializedExternalEvents1Events = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Link',
target: 'External events 2',
},
{
type: 'BuiltinCommonInstructions::Link',
target: 'External events 2',
},
]);
externalEvents1
.getEvents()
.unserializeFrom(project, serializedExternalEvents1Events);
const externalEvents2 = project.insertNewExternalEvents(
'External events 2',
0
);
const serializedExternalEvents2Events = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: ['Counter', '+', '1'],
},
],
events: [],
},
]);
externalEvents2
.getEvents()
.unserializeFrom(project, serializedExternalEvents2Events);
const serializedProjectElement = new gd.SerializerElement();
project.serializeTo(serializedProjectElement);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)),
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(2);
project.delete();
});
describe('(Scene) variable code generation', () => {
let project = null;
let layout = null;
beforeEach(() => {
project = new gd.ProjectHelper.createNewGDJSProject();
layout = project.insertNewLayout('Scene', 0);
layout.getObjects().insertNewObject(project, '', 'MyObject', 0);
// These variables are "simple" and their type will be known at code generation.
layout.getVariables().insertNew('MyNumberVariable', 0).setValue(123);
layout.getVariables().insertNew('MyStringVariable', 1).setString('Test');
layout
.getVariables()
.insertNew('MyOtherStringVariable', 1)
.setString('SomeChild');
// Use a variable that has a value deep inside a structure - so code generation
// does not know its type and will issue a `getAsNumberOrString`.
layout
.getVariables()
.insertNew('MyOtherStructureVariable', 1)
.getChild('Child')
.getChild('SubChild')
.setString('SomeOtherChild');
const structureVariable = layout
.getVariables()
.insertNew('MyStructureVariable', 2);
// Make a structure that we will address using other variables.
structureVariable.getChild('Test').setValue(1);
structureVariable.getChild('123').setValue(2);
structureVariable.getChild('42').setValue(4);
structureVariable.getChild('MyObject').setValue(8);
structureVariable.getChild('SomeChild').getChild('Test').setValue(16);
structureVariable.getChild('SomeChild').getChild('123').setValue(32);
structureVariable.getChild('SomeChild').getChild('42').setValue(64);
structureVariable
.getChild('SomeChild')
.getChild('MyObject')
.setValue(128);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('Test')
.setValue(256);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('123')
.setValue(512);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('42')
.setValue(1024);
structureVariable
.getChild('SomeChild')
.getChild('SomeOtherChild')
.getChild('MyObject')
.setValue(2048);
});
afterEach(() => {
project.delete();
});
it('generates code for structure variables accessed by another variable (1 level)', function () {
layout.getVariables().insertNew('Counter', 0).setValue(0);
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: [
'Counter',
'+',
'MyStructureVariable[MyStringVariable] + MyStructureVariable[MyNumberVariable] + MyStructureVariable[MyObject.X()] + MyStructureVariable[MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
1 + 2 + 4 + 8
);
});
it('generates code for structure variables accessed by another variable (2 levels)', function () {
layout.getVariables().insertNew('Counter', 0).setValue(0);
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: [
'Counter',
'+',
'MyStructureVariable.SomeChild[MyStringVariable] + MyStructureVariable.SomeChild[MyNumberVariable] + MyStructureVariable.SomeChild[MyObject.X()] + MyStructureVariable.SomeChild[MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
16 + 32 + 64 + 128
);
});
it('generates code for structure variables accessed by another variable (3 levels)', function () {
layout.getVariables().insertNew('Counter', 0).setValue(0);
const serializedLayoutEvents = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [],
actions: [
{
type: { value: 'SetNumberVariable' },
parameters: [
'Counter',
'+',
'MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyStringVariable] +MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyNumberVariable] + MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyObject.X()] + MyStructureVariable[MyOtherStringVariable][MyOtherStructureVariable.Child.SubChild][MyObject.ObjectName()]',
],
},
],
events: [],
},
]);
layout.getEvents().unserializeFrom(project, serializedLayoutEvents);
const runCompiledEvents = generateCompiledEventsForLayout(
gd,
project,
layout
);
const serializedSceneElement = new gd.SerializerElement();
layout.serializeTo(serializedSceneElement);
const { gdjs, runtimeScene } = makeMinimalGDJSMock({
sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)),
});
serializedSceneElement.delete();
const myObjectInstance = runtimeScene.createObject('MyObject');
myObjectInstance.setX(42);
runCompiledEvents(gdjs, runtimeScene);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
expect(runtimeScene.getVariables().get('Counter').getAsNumber()).toBe(
256 + 512 + 1024 + 2048
);
});
});
});