diff --git a/GDevelop.js/TestUtils/CodeGenerationHelpers.js b/GDevelop.js/TestUtils/CodeGenerationHelpers.js index 45f4e34a8339..42b5f4f1cf08 100644 --- a/GDevelop.js/TestUtils/CodeGenerationHelpers.js +++ b/GDevelop.js/TestUtils/CodeGenerationHelpers.js @@ -11,13 +11,39 @@ function generateCompiledEventsForEventsFunction( project, eventsFunction, logCode = false +) { + const extension = new gd.EventsFunctionsExtension(); + const runCompiledEventsFunction = generateCompiledEventsForEventsFunctionWithContext( + gd, + project, + extension, + eventsFunction, + logCode + ); + extension.delete(); + return runCompiledEventsFunction; +} + +/** + * Generate the code from events (using GDJS platform) + * and create a JavaScript function that runs it. + * + * The JavaScript function must be called with the `runtimeScene` to be used. + * In this context, GDJS game engine does not exist, so you must pass a mock + * to it to validate that the events are working properly. + */ +function generateCompiledEventsForEventsFunctionWithContext( + gd, + project, + extension, + eventsFunction, + logCode = false ) { const namespace = 'functionNamespace'; const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(project); const includeFiles = new gd.SetString(); - const extension = new gd.EventsFunctionsExtension(); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( extension, @@ -28,7 +54,6 @@ function generateCompiledEventsForEventsFunction( ); eventsFunctionsExtensionCodeGenerator.delete(); - extension.delete(); includeFiles.delete(); if (logCode) console.log(code); @@ -309,6 +334,70 @@ function generateCompiledEventsFromSerializedEvents( return runCompiledEvents; } +/** + * Helper to create compiled events from serialized events, creating a project and the events function. + * @param {*} gd + * @param {gdEventsFunctionExtension} extension + * @param {gdSerializerElement} eventsSerializerElement + * @param {{parameterTypes: {[name: string]: string}, groups: {[name: string]: string[]}, logCode: boolean}?} configuration + * @returns + */ +function generateCompiledEventsFunctionFromSerializedEvents( + gd, + extension, + eventsSerializerElement, + configuration +) { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunction = new gd.EventsFunction(); + eventsFunction.getEvents().unserializeFrom(project, eventsSerializerElement); + + if (configuration) { + const { parameterTypes, groups } = configuration; + if (groups) { + for (const groupName in groups) { + if (Object.hasOwnProperty.call(groups, groupName)) { + const objectsNames = groups[groupName]; + + const group = eventsFunction + .getObjectGroups() + .insertNew(groupName, 0); + for (const objectName of objectsNames) { + group.addObject(objectName); + } + } + } + } + + if (parameterTypes) { + for (const parameterName in parameterTypes) { + if (Object.hasOwnProperty.call(parameterTypes, parameterName)) { + const parameterType = parameterTypes[parameterName]; + + const parameter = new gd.ParameterMetadata(); + parameter.setType(parameterType); + parameter.setName(parameterName); + eventsFunction.getParameters().push_back(parameter); + parameter.delete(); + } + } + } + } + + const runCompiledEvents = generateCompiledEventsForEventsFunctionWithContext( + gd, + project, + extension, + eventsFunction, + configuration && configuration.logCode + ); + + eventsFunction.delete(); + project.delete(); + + return runCompiledEvents; +} + /** * Generate a function to run the compiled events of a layout. */ @@ -342,6 +431,7 @@ function generateCompiledEventsForLayout(gd, project, layout, logCode = false) { module.exports = { generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, + generateCompiledEventsFunctionFromSerializedEvents, generateCompiledEventsForSerializedEventsBasedExtension, generateCompiledEventsForLayout, }; diff --git a/GDevelop.js/TestUtils/GDJSMocks.js b/GDevelop.js/TestUtils/GDJSMocks.js index 3ac9c9a71e34..bac3dacd9821 100644 --- a/GDevelop.js/TestUtils/GDJSMocks.js +++ b/GDevelop.js/TestUtils/GDJSMocks.js @@ -682,11 +682,26 @@ class RuntimeGame { this._variablesContainer = new VariablesContainer( gameData && gameData.variables ); + this._variablesByExtensionName = new Map(); + if (gameData) { + for (const extensionData of gameData.eventsFunctionsExtensions) { + if (extensionData.globalVariables.length > 0) { + this._variablesByExtensionName.set( + extensionData.name, + new VariablesContainer(extensionData.globalVariables) + ); + } + } + } } getVariables() { return this._variablesContainer; } + + getVariablesForExtension(extensionName) { + return this._variablesByExtensionName.get(extensionName) || null; + } } /** A minimal implementation of gdjs.RuntimeScene for testing. */ @@ -696,6 +711,16 @@ class RuntimeScene { this._variablesContainer = new VariablesContainer( sceneData && sceneData.variables ); + this._variablesByExtensionName = new Map(); + if (sceneData && sceneData.usedExtensionsWithVariablesData) { + for (const extensionData of sceneData.usedExtensionsWithVariablesData) { + this._variablesByExtensionName.set( + extensionData.name, + new VariablesContainer(extensionData.sceneVariables) + ); + } + } + this._onceTriggers = new OnceTriggers(); this._asyncTasksManager = new FakeAsyncTasksManager(); @@ -760,6 +785,10 @@ class RuntimeScene { return this._variablesContainer; } + getVariablesForExtension(extensionName) { + return this._variablesByExtensionName.get(extensionName) || null; + } + getOnceTriggers() { return this._onceTriggers; } @@ -883,6 +912,10 @@ function makeMinimalGDJSMock(options) { const behaviorCtors = {}; const customObjectsCtors = {}; let runtimeScenePreEventsCallbacks = []; + if (options && options.gameData && options.sceneData) { + options.sceneData.usedExtensionsWithVariablesData = + options.gameData.eventsFunctionsExtensions; + } const runtimeGame = new RuntimeGame(options && options.gameData); const runtimeScene = new RuntimeScene( options && options.sceneData, diff --git a/GDevelop.js/__tests__/GDJSAdvancedExtensionCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSAdvancedExtensionCodeGenerationIntegrationTests.js index d7d37c3ddd9a..61a65718e2ef 100644 --- a/GDevelop.js/__tests__/GDJSAdvancedExtensionCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSAdvancedExtensionCodeGenerationIntegrationTests.js @@ -1,7 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, } = require('../TestUtils/CodeGenerationHelpers.js'); diff --git a/GDevelop.js/__tests__/GDJSBaseObjectCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSBaseObjectCodeGenerationIntegrationTests.js index eb3a3ed781fe..8d02011a7433 100644 --- a/GDevelop.js/__tests__/GDJSBaseObjectCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSBaseObjectCodeGenerationIntegrationTests.js @@ -1,7 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, } = require('../TestUtils/CodeGenerationHelpers.js'); diff --git a/GDevelop.js/__tests__/GDJSBooleanOperatorsCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSBooleanOperatorsCodeGenerationIntegrationTests.js index 11a1afc523fd..d78987a80575 100644 --- a/GDevelop.js/__tests__/GDJSBooleanOperatorsCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSBooleanOperatorsCodeGenerationIntegrationTests.js @@ -1,7 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, } = require('../TestUtils/CodeGenerationHelpers.js'); diff --git a/GDevelop.js/__tests__/GDJSCommonExtensionCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSCommonExtensionCodeGenerationIntegrationTests.js index 5ecf0f510c75..93e7d43d89f7 100644 --- a/GDevelop.js/__tests__/GDJSCommonExtensionCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSCommonExtensionCodeGenerationIntegrationTests.js @@ -1,7 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, } = require('../TestUtils/CodeGenerationHelpers.js'); diff --git a/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js index aae77c352992..d2413f731007 100644 --- a/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js @@ -1,6 +1,5 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, generateCompiledEventsForSerializedEventsBasedExtension, } = require('../TestUtils/CodeGenerationHelpers.js'); diff --git a/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js new file mode 100644 index 000000000000..7f91e95e1b25 --- /dev/null +++ b/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js @@ -0,0 +1,682 @@ +const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); +const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); +const { + generateCompiledEventsFunctionFromSerializedEvents, +} = require('../TestUtils/CodeGenerationHelpers.js'); + +describe('libGD.js - GDJS Code Generation integration tests', function () { + let gd = null; + beforeAll(async () => { + gd = await initializeGDevelopJs(); + }); + + let project = null; + let scene = null; + let extension = null; + beforeEach(() => { + project = new gd.ProjectHelper.createNewGDJSProject(); + extension = project.insertNewEventsFunctionsExtension('Extension', 0); + scene = project.insertNewLayout('Scene', 0); + }); + afterEach(() => { + project.delete(); + }); + + const generateAndRunActionsForFunction = (actions, options = {}) => { + return generateAndRunEventsForFunction( + [ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [], + actions, + events: [], + }, + ], + options + ); + }; + + const generateAndRunVariableAffectationWithConditions = ( + conditions, + options = {} + ) => { + extension.getSceneVariables().insertNew('SuccessVariable', 0).setValue(0); + return generateAndRunEventsForFunction( + [ + { + type: 'BuiltinCommonInstructions::Standard', + conditions, + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['SuccessVariable', '=', '1'], + }, + ], + events: [], + }, + ], + options + ); + }; + + const generateAndRunEventsForFunction = ( + events, + { beforeRunningEvents, logCode } = {} + ) => { + const serializedProjectElement = new gd.SerializerElement(); + project.serializeTo(serializedProjectElement); + + const serializedSceneElement = new gd.SerializerElement(); + scene.serializeTo(serializedSceneElement); + + const serializerElement = gd.Serializer.fromJSObject(events); + + const runCompiledEvents = generateCompiledEventsFunctionFromSerializedEvents( + gd, + extension, + serializerElement, + { logCode } + ); + + const { gdjs, runtimeScene } = makeMinimalGDJSMock({ + gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)), + sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)), + }); + serializedProjectElement.delete(); + + if (beforeRunningEvents) { + beforeRunningEvents(runtimeScene); + } + runCompiledEvents(gdjs, runtimeScene, []); + + return runtimeScene; + }; + + it('can generate a scene number variable action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(0); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '1'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a scene string variable action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setString(''); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'SetStringVariable' }, + parameters: ['MyVariable', '=', '"Hello"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsString() + ).toBe('Hello'); + }); + + it('can generate a scene boolean variable action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setBool(false); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'SetBooleanVariable' }, + parameters: ['MyVariable', 'True'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsBoolean() + ).toBe(true); + }); + + it('can generate a scene boolean variable toggle', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setBool(false); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'SetBooleanVariable' }, + parameters: ['MyVariable', 'Toggle'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsBoolean() + ).toBe(true); + }); + + it('can generate a scene number variable condition that is true', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(123); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyVariable', '=', '123'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a project number variable condition that is true', function () { + extension.getGlobalVariables().insertNew('MyVariable', 0).setValue(123); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyVariable', '=', '123'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a condition giving precedence to the scene variable', function () { + extension.getGlobalVariables().insertNew('MyVariable', 0).setValue(123); + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(456); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that is true', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setBool(true); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'BooleanVariable' }, + parameters: ['MyVariable'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a scene string variable condition that is true', function () { + extension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setString('Same value'); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'BooleanVariable' }, + parameters: ['MyVariable', '=', '"Same value"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a scene string variable condition that is false', function () { + extension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setString('Not the same'); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'StringVariable' }, + parameters: ['MyVariable', '=', '"Different"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(0); + }); + + it('can generate a string variable condition that is true with a contains operator', function () { + extension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setString('Hello world!'); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'StringVariable' }, + parameters: ['MyVariable', 'contains', '"world"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a string variable condition that is false with a contains operator', function () { + extension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setString('Hello world!'); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'StringVariable' }, + parameters: ['MyVariable', 'contains', '"Hi!"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(0); + }); + + it('can generate a push number action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).castTo('Array'); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'PushNumber' }, + parameters: ['MyVariable', '123'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getChild('0') + .getAsNumber() + ).toBe(123); + }); + + it('can generate a push string action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).castTo('Array'); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'PushString' }, + parameters: ['MyVariable', '"Hello"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getChild('0') + .getAsString() + ).toBe('Hello'); + }); + + it('can generate a push boolean action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).castTo('Array'); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'PushBoolean' }, + parameters: ['MyVariable', 'True'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getChild('0') + .getAsBoolean() + ).toBe(true); + }); + + it('can generate a push variable action', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).castTo('Array'); + extension.getSceneVariables().insertNew('MyOtherVariable', 0).setValue(123); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'PushVariable' }, + parameters: ['MyVariable', 'MyOtherVariable'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getChild('0') + .getAsNumber() + ).toBe(123); + }); + + it('can generate a local variable expression', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(0); + const runtimeScene = generateAndRunEventsForFunction([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [{ name: 'MyLocalVariable', type: 'number', value: 123 }], + conditions: [], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', 'MyLocalVariable'], + }, + ], + events: [], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsNumber() + ).toBe(123); + }); + + it('can generate a local variable condition', function () { + extension.getSceneVariables().insertNew('SuccessVariable', 0).setValue(0); + const runtimeScene = generateAndRunEventsForFunction([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [{ name: 'MyLocalVariable', type: 'number', value: 123 }], + conditions: [ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyLocalVariable', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['SuccessVariable', '=', '1'], + }, + ], + events: [], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a local variable condition giving precedence to the closest local variable', function () { + extension.getSceneVariables().insertNew('SuccessVariable', 0).setValue(0); + + extension.getGlobalVariables().insertNew('MyVariable', 0).setValue(123); + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(456); + const runtimeScene = generateAndRunEventsForFunction([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [{ name: 'MyLocalVariable', type: 'number', value: 789 }], + conditions: [], + actions: [], + events: [ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [ + { name: 'MyLocalVariable', type: 'number', value: 147 }, + ], + conditions: [ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyLocalVariable', '=', '147'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['SuccessVariable', '=', '1'], + }, + ], + events: [], + }, + ], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a local variable without affecting parent event local variables', function () { + extension.getSceneVariables().insertNew('SuccessVariable', 0).setValue(0); + + extension.getGlobalVariables().insertNew('MyVariable', 0).setValue(123); + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(456); + const runtimeScene = generateAndRunEventsForFunction([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [{ name: 'MyLocalVariable', type: 'number', value: 789 }], + conditions: [], + actions: [], + events: [ + // Create a new local variable with the same name. + { + type: 'BuiltinCommonInstructions::Standard', + variables: [ + { name: 'MyLocalVariable', type: 'number', value: 147 }, + ], + conditions: [], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyLocalVariable', '=', '148'], + }, + ], + }, + // The local variable must be untouched by the previous event. + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyLocalVariable', '=', '789'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['SuccessVariable', '=', '1'], + }, + ], + }, + ], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a condition on a local variable from a parent event', function () { + extension.getSceneVariables().insertNew('SuccessVariable', 0).setValue(0); + const runtimeScene = generateAndRunEventsForFunction([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [{ name: 'MyLocalVariable', type: 'number', value: 123 }], + conditions: [], + actions: [], + events: [ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [], + conditions: [ + { + type: { inverted: false, value: 'NumberVariable' }, + parameters: ['MyLocalVariable', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['SuccessVariable', '=', '1'], + }, + ], + events: [], + }, + ], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a VariableChildCount expression', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(0); + extension + .getSceneVariables() + .insertNew('MyStructureVariable', 0) + .getChild('MyChild') + .setValue(123); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'SetNumberVariable' }, + parameters: [ + 'MyVariable', + '=', + 'VariableChildCount(MyStructureVariable)', + ], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsNumber() + ).toBe(1); + }); + + // TODO Move this test with legacy ones. + it('can generate a VariableChildCount expression for an undeclared variable', function () { + extension.getSceneVariables().insertNew('MyVariable', 0).setValue(0); + const runtimeScene = generateAndRunActionsForFunction( + [ + { + type: { value: 'SetNumberVariable' }, + parameters: [ + 'MyVariable', + '=', + 'VariableChildCount(MyStructureVariable)', + ], + }, + ], + { + beforeRunningEvents: (runtimeScene) => { + runtimeScene + .getVariables() + .get('MyStructureVariable') + .getChild('MyChild') + .setValue(123); + }, + } + ); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a child existence condition that is true', function () { + extension + .getSceneVariables() + .insertNew('MyStructureVariable', 0) + .getChild('MyChild') + .setValue(123); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'VariableChildExists2' }, + parameters: ['MyStructureVariable', '"MyChild"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('SuccessVariable') + .getAsNumber() + ).toBe(1); + }); + + it('can generate a child removing action', function () { + const variable = extension + .getSceneVariables() + .insertNew('MyStructureVariable', 0); + variable.getChild('MyChildA').setValue(123); + variable.getChild('MyChildB').setValue(456); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'RemoveVariableChild' }, + parameters: ['MyStructureVariable', '"MyChildA"'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyStructureVariable') + .hasChild('MyChildA') + ).toBe(false); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyStructureVariable') + .hasChild('MyChildB') + ).toBe(true); + }); + + it('can generate a children clearing action', function () { + const variable = extension + .getSceneVariables() + .insertNew('MyStructureVariable', 0); + variable.getChild('MyChildA').setValue(123); + variable.getChild('MyChildB').setValue(123); + const runtimeScene = generateAndRunActionsForFunction([ + { + type: { value: 'ClearVariableChildren' }, + parameters: ['MyStructureVariable'], + }, + ]); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyStructureVariable') + .hasChild('MyChildA') + ).toBe(false); + expect( + runtimeScene + .getVariablesForExtension('Extension') + .get('MyStructureVariable') + .hasChild('MyChildB') + ).toBe(false); + }); +}); diff --git a/GDevelop.js/__tests__/GDJSLegacyVariableCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSLegacyVariableCodeGenerationIntegrationTests.js index 06c0cb80511c..d39420840dec 100644 --- a/GDevelop.js/__tests__/GDJSLegacyVariableCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSLegacyVariableCodeGenerationIntegrationTests.js @@ -1,7 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); const { - generateCompiledEventsForEventsFunction, generateCompiledEventsFromSerializedEvents, } = require('../TestUtils/CodeGenerationHelpers.js');