text: const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js');
  const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks');
  
  describe('libGD.js - GDJS Code Generation integration tests', function () {
    let gd = null;
    beforeAll((done) =>
      initializeGDevelopJs().then((module) => {
        gd = module;
        done();
      })
    );
  
    it('generates a working function for While event', function () {
      // Create nested events using And and StrEqual conditions
      const serializerElement = gd.Serializer.fromJSObject([
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['Counter', '=', '0'],
              subInstructions: [],
            },
          ],
          events: [],
        },
        {
          disabled: false,
          folded: false,
          infiniteLoopWarning: true,
          type: 'BuiltinCommonInstructions::While',
          whileConditions: [
            {
              type: { inverted: false, value: 'VarScene' },
              parameters: ['Counter', '<', '4'],
              subInstructions: [],
            },
          ],
          conditions: [],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['Counter', '+', '1'],
              subInstructions: [],
            },
          ],
          events: [],
        },
      ]);
  
      var 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(4);
    });
  
    it('generates a working function with nested Or and StrEqual', function () {
      // Create nested events using Or and StrEqual conditions
      const serializerElement = gd.Serializer.fromJSObject([
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [
            {
              type: {
                inverted: false,
                value: 'BuiltinCommonInstructions::Or',
              },
              parameters: [],
              subInstructions: [
                {
                  type: { inverted: false, value: 'Egal' },
                  parameters: ['1', '=', '2'],
                  subInstructions: [],
                },
                {
                  type: {
                    inverted: false,
                    value: 'BuiltinCommonInstructions::Or',
                  },
                  parameters: [],
                  subInstructions: [
                    // This should be true and make the entire conditions true.
                    {
                      type: { inverted: false, value: 'StrEqual' },
                      parameters: ['"1"', '=', '"1"'],
                      subInstructions: [],
                    },
                  ],
                },
                {
                  type: { inverted: false, value: 'StrEqual' },
                  parameters: ['"1"', '=', '"2"'],
                  subInstructions: [],
                },
              ],
            },
          ],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['SuccessVariable', '=', '1'],
              subInstructions: [],
            },
          ],
          events: [],
        },
      ]);
  
      var runCompiledEvents = generateCompiledEventsFromSerializedEvents(
        gd,
        serializerElement
      );
  
      const { gdjs, runtimeScene } = makeMinimalGDJSMock();
      runCompiledEvents(gdjs, runtimeScene, []);
  
      expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
      expect(
        runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
      ).toBe(1);
    });
  
    it('generates a working function with nested And and StrEqual', function () {
      // Create nested events using And and StrEqual conditions
      const serializerElement = gd.Serializer.fromJSObject([
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [
            {
              type: {
                inverted: false,
                value: 'BuiltinCommonInstructions::And',
              },
              parameters: [],
              subInstructions: [
                {
                  type: { inverted: false, value: 'Egal' },
                  parameters: ['1', '=', '1'],
                  subInstructions: [],
                },
                {
                  type: {
                    inverted: false,
                    value: 'BuiltinCommonInstructions::And',
                  },
                  parameters: [],
                  subInstructions: [
                    {
                      type: { inverted: false, value: 'Egal' },
                      parameters: ['1', '=', '1'],
                      subInstructions: [],
                    },
                    {
                      type: {
                        inverted: false,
                        value: 'BuiltinCommonInstructions::And',
                      },
                      parameters: [],
                      subInstructions: [
                        {
                          type: { inverted: false, value: 'Egal' },
                          parameters: ['1', '=', '1'],
                          subInstructions: [],
                        },
                        {
                          type: { inverted: false, value: 'StrEqual' },
                          parameters: ['"1"', '=', '"1"'],
                          subInstructions: [],
                        },
                      ],
                    },
                    {
                      type: { inverted: false, value: 'StrEqual' },
                      parameters: ['"1"', '=', '"1"'],
                      subInstructions: [],
                    },
                  ],
                },
                {
                  type: { inverted: false, value: 'StrEqual' },
                  parameters: ['"1"', '=', '"1"'],
                  subInstructions: [],
                },
              ],
            },
          ],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['SuccessVariable', '=', '1'],
              subInstructions: [],
            },
          ],
          events: [],
        },
      ]);
  
      var runCompiledEvents = generateCompiledEventsFromSerializedEvents(
        gd,
        serializerElement
      );
  
      const { gdjs, runtimeScene } = makeMinimalGDJSMock();
      runCompiledEvents(gdjs, runtimeScene, []);
  
      expect(runtimeScene.getVariables().has('SuccessVariable')).toBe(true);
      expect(
        runtimeScene.getVariables().get('SuccessVariable').getAsNumber()
      ).toBe(1);
    });
  
    it('generates a working function creating objects', function () {
      const makeAddOneToObjectTestVariableEvent = (objectName) => ({
        disabled: false,
        folded: false,
        type: 'BuiltinCommonInstructions::Standard',
        conditions: [],
        actions: [
          {
            type: { inverted: false, value: 'ModVarObjet' },
            parameters: [objectName, 'TestVariable', '+', '1'],
            subInstructions: [],
          },
        ],
        events: [],
      });
  
      // Create events using the Trigger Once condition.
      const eventsSerializerElement = gd.Serializer.fromJSObject([
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [],
          actions: [
            {
              type: { inverted: false, value: 'Create' },
              parameters: ['', 'MyObjectA', '0', '0', ''],
              subInstructions: [],
            },
          ],
          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:
      const objectParameter = new gd.ParameterMetadata();
      objectParameter.setType('object');
      objectParameter.setName('MyObjectA');
      eventsFunction.getParameters().push_back(objectParameter);
      objectParameter.setType('object');
      objectParameter.setName('MyObjectB');
      eventsFunction.getParameters().push_back(objectParameter);
      objectParameter.delete();
  
      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 a working function with BuiltinCommonInstructions::Once', function () {
      // Event to create an object, then add
      const eventsSerializerElement = gd.Serializer.fromJSObject([
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [
            {
              type: {
                inverted: false,
                value: 'BuiltinCommonInstructions::Once',
              },
              parameters: [],
              subInstructions: [],
            },
          ],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['SuccessVariable', '+', '1'],
              subInstructions: [],
            },
          ],
          events: [],
        },
        {
          disabled: false,
          folded: false,
          type: 'BuiltinCommonInstructions::Standard',
          conditions: [
            {
              type: {
                inverted: false,
                value: 'BuiltinCommonInstructions::Once',
              },
              parameters: [],
              subInstructions: [],
            },
          ],
          actions: [
            {
              type: { inverted: false, value: 'ModVarScene' },
              parameters: ['SuccessVariable', '+', '1'],
              subInstructions: [],
            },
          ],
          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([
          {
            disabled: false,
            folded: false,
            type: 'BuiltinCommonInstructions::Standard',
            conditions: [
              {
                type: {
                  inverted: false,
                  value: 'BuiltinCommonInstructions::Once',
                },
                parameters: [],
                subInstructions: [],
              },
            ],
            actions: [
              {
                type: { inverted: false, value: 'ModVarScene' },
                parameters: ['SuccessVariable', '+', '1'],
                subInstructions: [],
              },
            ],
            events: [],
          },
          {
            disabled: false,
            folded: false,
            type: 'BuiltinCommonInstructions::Standard',
            conditions: [
              {
                type: {
                  inverted: false,
                  value: 'BuiltinCommonInstructions::Once',
                },
                parameters: [],
                subInstructions: [],
              },
            ],
            actions: [
              {
                type: { inverted: false, value: 'ModVarScene' },
                parameters: ['SuccessVariable', '+', '1'],
                subInstructions: [],
              },
            ],
            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();
    });
  });
  
  /**
   * 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 generateCompiledEventsForEventsFunction(gd, project, eventsFunction) {
    const namespace = 'functionNamespace';
    const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(
      project
    );
  
    const includeFiles = new gd.SetString();
    const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode(
      eventsFunction,
      namespace,
      includeFiles,
      true
    );
  
    eventsFunctionsExtensionCodeGenerator.delete();
    includeFiles.delete();
  
    // Uncomment to see the generated code:
    // console.log(code);
  
    // Create a "real" JavaScript function with the generated code.
    const runCompiledEventsFunction = new Function(
      'gdjs',
      'runtimeScene',
      'functionArguments',
      // Expose some global variables that are expected by the generated code:
      `Hashtable = gdjs.Hashtable;` +
        '\n' +
        code +
        // Return the function for it to be called.
        `;
  return functionNamespace.func.apply(functionNamespace.func, [runtimeScene, ...functionArguments, runtimeScene]);`
    );
  
    return runCompiledEventsFunction;
  }
  
  /** Helper to create compiled events from serialized events, creating a project and the events function. */
  function generateCompiledEventsFromSerializedEvents(
    gd,
    eventsSerializerElement
  ) {
    const project = new gd.ProjectHelper.createNewGDJSProject();
    const eventsFunction = new gd.EventsFunction();
    eventsFunction.getEvents().unserializeFrom(project, eventsSerializerElement);
  
    const runCompiledEvents = generateCompiledEventsForEventsFunction(
      gd,
      project,
      eventsFunction
    );
  
    eventsFunction.delete();
    project.delete();
  
    return runCompiledEvents;
  }
  