const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const path = require('path'); const { makeFakeAbstractFileSystem, } = require('../TestUtils/FakeAbstractFileSystem'); const extend = require('extend'); const mapFor = /*:: */ ( start /*: number */, end /*: number */, func /*: (number) => T */ ) /*: Array */ => { const result = []; for (let i = start; i < end; i++) { result.push(func(i)); } return result; }; describe('libGD.js', function () { let gd = null; beforeAll(async () => { gd = await initializeGDevelopJs(); }); describe('gd.VersionWrapper', function () { it('can return the version number of the library', function () { expect(typeof gd.VersionWrapper.major()).toBe('number'); expect(typeof gd.VersionWrapper.minor()).toBe('number'); expect(typeof gd.VersionWrapper.build()).toBe('number'); expect(typeof gd.VersionWrapper.revision()).toBe('number'); expect(typeof gd.VersionWrapper.fullString()).toBe('string'); }); }); describe('gd.Project', function () { let project = null; beforeAll(() => (project = gd.ProjectHelper.createNewGDJSProject())); it('has properties that can be read and changed', function () { project.setName('My super project'); expect(project.getName()).toBe('My super project'); project.setDescription("This is a great game I'm really proud of"); expect(project.getDescription()).toBe( "This is a great game I'm really proud of" ); project.setVersion('1.2.34'); expect(project.getVersion()).toBe('1.2.34'); project.setPackageName('com.test.package'); expect(project.getPackageName()).toBe('com.test.package'); project.setOrientation('portrait'); expect(project.getOrientation()).toBe('portrait'); project.setOrientation('landscape'); expect(project.getOrientation()).toBe('landscape'); project.setAuthor('Me'); expect(project.getAuthor()).toBe('Me'); project.setMaximumFPS(15); expect(project.getMaximumFPS()).toBe(15); project.setMinimumFPS(15); expect(project.getMinimumFPS()).toBe(15); project.setFolderProject(true); expect(project.isFolderProject()).toBe(true); project.setFolderProject(false); expect(project.isFolderProject()).toBe(false); }); it('can store loading screen setup', function () { project.getLoadingScreen().showGDevelopLogoDuringLoadingScreen(true); expect( project.getLoadingScreen().isGDevelopLogoShownDuringLoadingScreen() ).toBe(true); project.getLoadingScreen().showGDevelopLogoDuringLoadingScreen(false); expect( project.getLoadingScreen().isGDevelopLogoShownDuringLoadingScreen() ).toBe(false); }); it('handles layouts', function () { expect(project.hasLayoutNamed('Scene')).toBe(false); project.insertNewLayout('Scene', 0); expect(project.hasLayoutNamed('Scene')).toBe(true); expect(project.getLayout('Scene').getName()).toBe('Scene'); project.removeLayout('Scene'); expect(project.hasLayoutNamed('Scene')).toBe(false); }); it('handles external events', function () { expect(project.hasExternalEventsNamed('My events')).toBe(false); project.insertNewExternalEvents('My events', 0); expect(project.hasExternalEventsNamed('My events')).toBe(true); expect(project.getExternalEvents('My events').getName()).toBe( 'My events' ); project.removeExternalEvents('My events'); expect(project.hasExternalEventsNamed('My events')).toBe(false); }); it('handles external layouts', function () { expect(project.hasExternalLayoutNamed('My layout')).toBe(false); project.insertNewExternalLayout('My layout', 0); expect(project.hasExternalLayoutNamed('My layout')).toBe(true); expect(project.getExternalLayout('My layout').getName()).toBe( 'My layout' ); project.removeExternalLayout('My layout'); expect(project.hasExternalLayoutNamed('My layout')).toBe(false); }); it('should validate object names', function () { expect(gd.Project.isNameSafe('ThisNameIs_Ok_123')).toBe(true); expect(gd.Project.isNameSafe('ThisNameIs_👍_123')).toBe(true); expect(gd.Project.isNameSafe('ThisName IsNot_Ok_123')).toBe(false); expect(gd.Project.isNameSafe('ThisName()IsNot_Ok_123')).toBe(false); expect(gd.Project.isNameSafe('ThisNameIsNot_Ok!')).toBe(false); expect(gd.Project.isNameSafe('1ThisNameIsNot_Ok_123')).toBe(false); expect(gd.Project.getSafeName('ThisNameIs_Ok_123')).toBe( 'ThisNameIs_Ok_123' ); expect(gd.Project.getSafeName('ThisNameIs_👍_123')).toBe( 'ThisNameIs_👍_123' ); expect(gd.Project.getSafeName('ThisName IsNot_Ok_123')).toBe( 'ThisName_IsNot_Ok_123' ); expect(gd.Project.getSafeName('ThisName()IsNot_Ok_123')).toBe( 'ThisName__IsNot_Ok_123' ); expect(gd.Project.getSafeName('ThisNameIsNot_Ok!')).toBe( 'ThisNameIsNot_Ok_' ); expect(gd.Project.getSafeName('1ThisNameIsNot_Ok_123')).toBe( '_1ThisNameIsNot_Ok_123' ); expect(gd.Project.getSafeName('官话 name')).toBe('官话_name'); expect(gd.Project.getSafeName('')).toBe('Unnamed'); expect(gd.Project.getSafeName('9')).toBe('_9'); expect(gd.Project.getSafeName('ExplosionParticles3D')).toBe( 'ExplosionParticles3D' ); }); it('should have a list of extensions', function () { expect( gd.UsedExtensionsFinder.scanProject(project) .getUsedExtensions() .toNewVectorString() .toJSArray() ).toEqual([ // This extension is always considerde as used, as it // includes files necessary for the in-game editor. 'BuiltinObject', ]); project.getObjects().insertNewObject(project, 'Sprite', 'MyObject', 0); expect( gd.UsedExtensionsFinder.scanProject(project) .getUsedExtensions() .toNewVectorString() .toJSArray() ).toEqual([ 'AnimatableCapability', 'BuiltinObject', 'EffectCapability', 'FlippableCapability', 'OpacityCapability', 'ResizableCapability', 'ScalableCapability', 'Sprite', ]); }); it('handles events functions extensions', function () { expect(project.hasEventsFunctionsExtensionNamed('Ext')).toBe(false); project.insertNewEventsFunctionsExtension('Ext', 0); expect(project.hasEventsFunctionsExtensionNamed('Ext')).toBe(true); expect(project.getEventsFunctionsExtension('Ext').getName()).toBe('Ext'); project.removeEventsFunctionsExtension('Ext'); expect(project.hasEventsFunctionsExtensionNamed('Ext')).toBe(false); }); afterAll(function () { project.delete(); }); }); describe('gd.Layout', function () { let project = null; let layout = null; beforeAll(() => { project = gd.ProjectHelper.createNewGDJSProject(); layout = project.insertNewLayout('Scene', 0); }); it('can have a new name', function () { expect(layout.getName()).toBe('Scene'); layout.setName('My super layout'); expect(layout.getName()).toBe('My super layout'); }); it('can have a name with UTF8 characters', function () { layout.setName('Scene with a 官话 name'); expect(layout.getName()).toBe('Scene with a 官话 name'); }); it('can store events', function () { let evts = layout.getEvents(); expect(evts.getEventsCount()).toBe(0); let evt = evts.insertNewEvent( project, 'BuiltinCommonInstructions::Standard', 0 ); expect(evts.getEventsCount()).toBe(1); evt .getSubEvents() .insertNewEvent(project, 'BuiltinCommonInstructions::Standard', 0); expect(evts.getEventAt(0).getSubEvents().getEventsCount()).toBe(1); }); it('can have objects', function () { let object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); let object2 = layout .getObjects() .insertNewObject(project, 'TextObject::Text', 'MyObject2', 1); expect(layout.getObjects().getObjectAt(0).ptr).toBe(object.ptr); expect(layout.getObjects().getObjectAt(1).ptr).toBe(object2.ptr); expect(layout.getObjects().getObjectAt(0).getType()).toBe('Sprite'); expect(layout.getObjects().getObjectAt(1).getType()).toBe( 'TextObject::Text' ); }); afterAll(function () { project.delete(); }); }); describe('gd.Layer', function () { it('can have a name and visibility', function () { const layer = new gd.Layer(); layer.setName('GUI'); layer.setVisibility(false); expect(layer.getName()).toBe('GUI'); expect(layer.getVisibility()).toBe(false); layer.delete(); }); it('can have effects', function () { const layer = new gd.Layer(); const effects = layer.getEffects(); expect(effects.hasEffectNamed('EffectThatDoesNotExist')).toBe(false); expect(effects.getEffectsCount()).toBe(0); expect(effects.hasEffectNamed('EffectThatDoesNotExist')).toBe(false); expect(effects.getEffectsCount()).toBe(0); effects.insertNewEffect('MyEffect', 0); expect(effects.hasEffectNamed('EffectThatDoesNotExist')).toBe(false); expect(effects.hasEffectNamed('MyEffect')).toBe(true); expect(effects.getEffectsCount()).toBe(1); expect(effects.getEffectPosition('MyEffect')).toBe(0); const effect2 = new gd.Effect(); effect2.setName('MyEffect2'); effects.insertEffect(effect2, 1); expect(effects.hasEffectNamed('MyEffect2')).toBe(true); expect(effects.getEffectsCount()).toBe(2); expect(effects.getEffectPosition('MyEffect')).toBe(0); expect(effects.getEffectPosition('MyEffect2')).toBe(1); effects.swapEffects(0, 1); expect(effects.getEffectPosition('MyEffect2')).toBe(0); expect(effects.getEffectPosition('MyEffect')).toBe(1); const effect3 = new gd.Effect(); effect3.setName('MyEffect3'); effects.insertEffect(effect3, 2); expect(effects.hasEffectNamed('MyEffect3')).toBe(true); expect(effects.getEffectsCount()).toBe(3); effects.moveEffect(2, 0); expect(effects.getEffectPosition('MyEffect3')).toBe(0); expect(effects.getEffectPosition('MyEffect2')).toBe(1); expect(effects.getEffectPosition('MyEffect')).toBe(2); layer.delete(); }); it('can be serialized', function () { const layer = new gd.Layer(); const layer2 = new gd.Layer(); layer.setName('GUI'); layer.setVisibility(false); layer.getEffects().insertNewEffect('MyEffect', 0); layer.setCameraCount(1); const element = new gd.SerializerElement(); layer.serializeTo(element); for (let i = 0; i < 5; ++i) { // Repeat multiple time to check idempotency. layer2.unserializeFrom(element); expect(layer2.getName()).toBe('GUI'); expect(layer2.getVisibility()).toBe(false); expect(layer2.getEffects().getEffectsCount()).toBe(1); expect(layer2.getEffects().getEffectAt(0).getName()).toBe('MyEffect'); expect(layer2.getCameraCount()).toBe(1); } layer.delete(); layer2.delete(); }); }); describe('gd.Effect', function () { it('can have a name, effect name and parameters', function () { const effect = new gd.Effect(); effect.setName('MyEffect'); effect.setEffectType('Sepia'); expect(effect.getName()).toBe('MyEffect'); expect(effect.getEffectType()).toBe('Sepia'); effect.setDoubleParameter('Brightness', 1); effect.setDoubleParameter('Darkness', 0.3); effect.setDoubleParameter('Param3', 6); expect(effect.getAllDoubleParameters().keys().size()).toBe(3); expect(effect.getDoubleParameter('Brightness')).toBe(1); expect(effect.getDoubleParameter('Darkness')).toBe(0.3); expect(effect.getDoubleParameter('Param3')).toBe(6); effect.setStringParameter('SomeImage', 'myImageResource'); expect(effect.getStringParameter('SomeImage')).toBe('myImageResource'); expect(effect.getAllStringParameters().keys().size()).toBe(1); effect.setBooleanParameter('SomeBoolean', true); expect(effect.getBooleanParameter('SomeBoolean')).toBe(true); effect.setBooleanParameter('SomeBoolean', false); expect(effect.getBooleanParameter('SomeBoolean')).toBe(false); expect(effect.getAllBooleanParameters().keys().size()).toBe(1); effect.delete(); }); }); describe('gd.ObjectsContainer (using gd.Layout)', function () { let project = null; beforeAll(() => (project = gd.ProjectHelper.createNewGDJSProject())); it('can move objects', function () { let layout = project.insertNewLayout('Scene', 0); let object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); let object2 = layout .getObjects() .insertNewObject(project, 'TextObject::Text', 'MyObject2', 1); let object3 = layout .getObjects() .insertNewObject(project, 'TextObject::Text', 'MyObject3', 2); expect(layout.getObjects().getObjectAt(0).getName()).toBe('MyObject'); expect(layout.getObjects().getObjectAt(1).getName()).toBe('MyObject2'); expect(layout.getObjects().getObjectAt(2).getName()).toBe('MyObject3'); layout.getObjects().moveObject(0, 2); expect(layout.getObjects().getObjectAt(0).getName()).toBe('MyObject2'); expect(layout.getObjects().getObjectAt(1).getName()).toBe('MyObject3'); expect(layout.getObjects().getObjectAt(2).getName()).toBe('MyObject'); layout.getObjects().moveObject(0, 0); expect(layout.getObjects().getObjectAt(0).getName()).toBe('MyObject2'); expect(layout.getObjects().getObjectAt(1).getName()).toBe('MyObject3'); expect(layout.getObjects().getObjectAt(2).getName()).toBe('MyObject'); layout.getObjects().moveObject(1, 0); expect(layout.getObjects().getObjectAt(0).getName()).toBe('MyObject3'); expect(layout.getObjects().getObjectAt(1).getName()).toBe('MyObject2'); expect(layout.getObjects().getObjectAt(2).getName()).toBe('MyObject'); layout.getObjects().moveObject(0, 999); expect(layout.getObjects().getObjectAt(0).getName()).toBe('MyObject3'); expect(layout.getObjects().getObjectAt(1).getName()).toBe('MyObject2'); expect(layout.getObjects().getObjectAt(2).getName()).toBe('MyObject'); }); it('can find position of objects', function () { let layout = project.insertNewLayout('Scene2', 0); let object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); let object2 = layout .getObjects() .insertNewObject(project, 'TextObject::Text', 'MyObject2', 1); let object3 = layout .getObjects() .insertNewObject(project, 'TextObject::Text', 'MyObject3', 2); expect(layout.getObjects().getObjectPosition('MyObject')).toBe(0); expect(layout.getObjects().getObjectPosition('MyObject2')).toBe(1); expect(layout.getObjects().getObjectPosition('MyObject3')).toBe(2); expect(layout.getObjects().getObjectPosition('MyObject4')).toBe(-1); }); afterAll(function () { project.delete(); }); }); describe('gd.ObjectsContainer', function () { it('can move objects between containers, without moving them in memory', function () { const project = new gd.ProjectHelper.createNewGDJSProject(); // Prepare two containers, one with 3 objects and one empty const objectsContainer1 = new gd.ObjectsContainer(gd.ObjectsContainer.Unknown); const rootFolder1 = objectsContainer1.getRootFolder(); const objectsContainer2 = new gd.ObjectsContainer(gd.ObjectsContainer.Unknown); const rootFolder2 = objectsContainer2.getRootFolder(); const subFolder2 = rootFolder2.insertNewFolder('Folder', 1); const mySpriteObject = objectsContainer1.insertNewObject( project, 'Sprite', 'MySprite', 0 ); const mySprite2Object = objectsContainer1.insertNewObject( project, 'Sprite', 'MySprite2', 1 ); const mySprite3Object = objectsContainer1.insertNewObject( project, 'Sprite', 'MySprite3', 2 ); expect(objectsContainer1.getObjectsCount()).toBe(3); expect(objectsContainer2.getObjectsCount()).toBe(0); expect(rootFolder1.getChildrenCount()).toBe(3); expect(rootFolder2.getChildrenCount()).toBe(1); // Find the pointer to the objects in memory const mySpriteObjectPtr = gd.getPointer(objectsContainer1.getObjectAt(0)); const mySprite2ObjectPtr = gd.getPointer( objectsContainer1.getObjectAt(1) ); const mySprite3ObjectPtr = gd.getPointer( objectsContainer1.getObjectAt(2) ); const mySpriteObjectFolderOrObject = rootFolder1.getChildAt(0); const mySprite2ObjectFolderOrObject = rootFolder1.getChildAt(1); const mySprite3ObjectFolderOrObject = rootFolder1.getChildAt(2); const mySpriteObjectFolderOrObjectPtr = gd.getPointer( mySpriteObjectFolderOrObject ); const mySprite2ObjectFolderOrObjectPtr = gd.getPointer( mySprite2ObjectFolderOrObject ); const mySprite3ObjectFolderOrObjectPtr = gd.getPointer( mySprite3ObjectFolderOrObject ); // Move objects between containers objectsContainer1.moveObjectFolderOrObjectToAnotherContainerInFolder( mySprite2ObjectFolderOrObject, objectsContainer2, rootFolder2, 0 ); expect(objectsContainer1.getObjectsCount()).toBe(2); expect(objectsContainer1.getObjectAt(0).getName()).toBe('MySprite'); expect(objectsContainer1.getObjectAt(1).getName()).toBe('MySprite3'); expect(objectsContainer2.getObjectsCount()).toBe(1); expect(objectsContainer2.getObjectAt(0).getName()).toBe('MySprite2'); expect(rootFolder2.hasObjectNamed('MySprite2')).toBe(true); expect(rootFolder2.getChildrenCount()).toBe(2); expect(gd.getPointer(rootFolder2.getObjectChild('MySprite2'))).toBe( mySprite2ObjectFolderOrObjectPtr ); expect(rootFolder2.getObjectChild('MySprite2')).toBe( mySprite2ObjectFolderOrObject ); expect(mySprite2ObjectFolderOrObject.getParent()).toBe(rootFolder2); // Move object in sub folder. objectsContainer1.moveObjectFolderOrObjectToAnotherContainerInFolder( mySprite3ObjectFolderOrObject, objectsContainer2, subFolder2, 0 ); expect(objectsContainer1.getObjectsCount()).toBe(1); expect(objectsContainer1.getObjectAt(0).getName()).toBe('MySprite'); expect(objectsContainer2.getObjectsCount()).toBe(2); expect(objectsContainer2.getObjectAt(0).getName()).toBe('MySprite2'); expect(objectsContainer2.getObjectAt(1).getName()).toBe('MySprite3'); expect(subFolder2.hasObjectNamed('MySprite3')).toBe(true); expect(subFolder2.getChildrenCount()).toBe(1); expect(gd.getPointer(subFolder2.getObjectChild('MySprite3'))).toBe( mySprite3ObjectFolderOrObjectPtr ); expect(mySprite3ObjectFolderOrObject.getParent()).toBe(subFolder2); // Check that the object in memory are the same, even if moved to another container expect(gd.getPointer(objectsContainer1.getObjectAt(0))).toBe( mySpriteObjectPtr ); expect(gd.getPointer(objectsContainer2.getObjectAt(0))).toBe( mySprite2ObjectPtr ); expect(gd.getPointer(objectsContainer2.getObjectAt(1))).toBe( mySprite3ObjectPtr ); expect(gd.getPointer(rootFolder2.getObjectChild('MySprite2'))).toBe( mySprite2ObjectFolderOrObjectPtr ); expect(rootFolder2.getObjectChild('MySprite2')).toBe( mySprite2ObjectFolderOrObject ); // Move back first object to first container objectsContainer2.moveObjectFolderOrObjectToAnotherContainerInFolder( mySprite2ObjectFolderOrObject, objectsContainer1, rootFolder1, 0 ); expect(objectsContainer1.getObjectsCount()).toBe(2); expect(objectsContainer1.getObjectAt(0).getName()).toBe('MySprite'); expect(objectsContainer1.getObjectAt(1).getName()).toBe('MySprite2'); expect(objectsContainer2.getObjectsCount()).toBe(1); expect(objectsContainer2.getObjectAt(0).getName()).toBe('MySprite3'); expect(rootFolder2.hasObjectNamed('MySprite2')).toBe(false); expect(rootFolder2.getChildrenCount()).toBe(1); expect(rootFolder1.getChildrenCount()).toBe(2); expect(rootFolder1.getChildAt(0).getObject().getName()).toBe('MySprite2'); expect(rootFolder1.getChildAt(1).getObject().getName()).toBe('MySprite'); expect(rootFolder1.hasObjectNamed('MySprite2')).toBe(true); expect(mySprite2ObjectFolderOrObject.getParent()).toBe(rootFolder1); // Check again that the object in memory are the same, even if moved to another container expect(gd.getPointer(objectsContainer1.getObjectAt(0))).toBe( mySpriteObjectPtr ); expect(gd.getPointer(objectsContainer1.getObjectAt(1))).toBe( mySprite2ObjectPtr ); expect(gd.getPointer(objectsContainer2.getObjectAt(0))).toBe( mySprite3ObjectPtr ); expect(gd.getPointer(rootFolder1.getObjectChild('MySprite2'))).toBe( mySprite2ObjectFolderOrObjectPtr ); expect(gd.getPointer(rootFolder1.getObjectChild('MySprite'))).toBe( mySpriteObjectFolderOrObjectPtr ); expect(gd.getPointer(subFolder2.getObjectChild('MySprite3'))).toBe( mySprite3ObjectFolderOrObjectPtr ); project.delete(); }); it('enumerates folders and objects', function () { const project = new gd.ProjectHelper.createNewGDJSProject(); // Prepare two containers, one with 3 objects and one empty const objectsContainer = new gd.ObjectsContainer(gd.ObjectsContainer.Unknown); const rootFolder = objectsContainer.getRootFolder(); const folder = rootFolder.insertNewFolder('Folder 1', 0); const mySpriteObject = objectsContainer.insertNewObjectInFolder( project, 'Sprite', 'MySprite', folder, 0 ); const subFolder = folder.insertNewFolder('Sub Folder 1', 1); const mySprite2Object = objectsContainer.insertNewObjectInFolder( project, 'Sprite', 'MySprite2', subFolder, 0 ); const mySprite3Object = objectsContainer.insertNewObjectInFolder( project, 'Sprite', 'MySprite3', subFolder, 1 ); const subSubFolder = subFolder.insertNewFolder('Sub Sub Folder 1', 2); expect(objectsContainer.getObjectsCount()).toBe(3); expect(rootFolder.getChildrenCount()).toBe(1); expect(folder.getChildrenCount()).toBe(2); expect(subFolder.getChildrenCount()).toBe(3); const vectorObjectFolderOrObjects = objectsContainer.getAllObjectFolderOrObjects(); expect(vectorObjectFolderOrObjects.size()).toBe(6); expect(gd.getPointer(vectorObjectFolderOrObjects.at(0))).toBe( gd.getPointer(folder) ); expect(gd.getPointer(vectorObjectFolderOrObjects.at(1))).toBe( gd.getPointer(folder.getChildAt(0)) ); expect(gd.getPointer(vectorObjectFolderOrObjects.at(2))).toBe( gd.getPointer(subFolder) ); expect(gd.getPointer(vectorObjectFolderOrObjects.at(3))).toBe( gd.getPointer(subFolder.getChildAt(0)) ); expect(gd.getPointer(vectorObjectFolderOrObjects.at(4))).toBe( gd.getPointer(subFolder.getChildAt(1)) ); expect(gd.getPointer(vectorObjectFolderOrObjects.at(5))).toBe( gd.getPointer(subSubFolder) ); objectsContainer.delete(); project.delete(); }); }); describe('gd.InitialInstancesContainer', function () { let container = null; let containerCopy = null; beforeAll(() => { container = new gd.InitialInstancesContainer(); }); it('initial state', function () { expect(container.getInstancesCount()).toBe(0); }); it('adding instances', function () { let instance = container.insertNewInitialInstance(); instance.setObjectName('MyObject1'); instance.setZOrder(10); let instance2 = new gd.InitialInstance(); instance2.setObjectName('MyObject2'); instance2 = container.insertInitialInstance(instance2); let instance3 = container.insertNewInitialInstance(); instance3.setObjectName('MyObject3'); instance3.setZOrder(-1); instance3.setLayer('OtherLayer'); expect(container.getInstancesCount()).toBe(3); }); it('iterating', function () { let i = 0; let functor = new gd.InitialInstanceJSFunctor(); functor.invoke = function (instance) { instance = gd.wrapPointer(instance, gd.InitialInstance); expect( (i === 0 && instance.getObjectName() === 'MyObject1') || (i === 1 && instance.getObjectName() === 'MyObject2') || (i === 2 && instance.getObjectName() === 'MyObject3') ).toBe(true); i++; }; container.iterateOverInstances(functor); }); it('can rename instances', function () { container.renameInstancesOfObject('MyObject1', 'MyObject'); let i = 0; let functor = new gd.InitialInstanceJSFunctor(); functor.invoke = function (instance) { instance = gd.wrapPointer(instance, gd.InitialInstance); expect( (i === 0 && instance.getObjectName() === 'MyObject') || (i === 1 && instance.getObjectName() === 'MyObject2') || (i === 2 && instance.getObjectName() === 'MyObject3') ).toBe(true); i++; }; container.iterateOverInstances(functor); }); it('iterating with z ordering', function () { let i = 0; let functor = new gd.InitialInstanceJSFunctor(); functor.invoke = function (instance) { instance = gd.wrapPointer(instance, gd.InitialInstance); expect( (i === 0 && instance.getObjectName() === 'MyObject2') || (i === 1 && instance.getObjectName() === 'MyObject') ).toBe(true); i++; }; container.iterateOverInstancesWithZOrdering(functor, ''); }); it('moving from layers to another', function () { container.moveInstancesToLayer('OtherLayer', 'YetAnotherLayer'); let functor = new gd.InitialInstanceJSFunctor(); functor.invoke = function (instance) { instance = gd.wrapPointer(instance, gd.InitialInstance); expect(instance.getObjectName()).toBe('MyObject3'); }; container.iterateOverInstancesWithZOrdering(functor, 'YetAnotherLayer'); }); it('can be cloned', function () { containerCopy = container.clone(); expect(containerCopy.getInstancesCount()).toBe(3); let instance = containerCopy.insertNewInitialInstance(); instance.setObjectName('MyObject4'); expect(containerCopy.getInstancesCount()).toBe(4); expect(container.getInstancesCount()).toBe(3); containerCopy.delete(); containerCopy = null; }); it('removing instances', function () { container.removeInitialInstancesOfObject('MyObject'); expect(container.getInstancesCount()).toBe(2); }); it('removing instances on a layer', function () { container.removeAllInstancesOnLayer('YetAnotherLayer'); expect(container.getInstancesCount()).toBe(1); }); it('can be serialized', function () { expect(container.serializeTo).not.toBe(undefined); expect(container.unserializeFrom).not.toBe(undefined); }); afterAll(function () { container.delete(); }); }); describe('gd.InitialInstance', function () { let project = null; let layout = null; let initialInstance = null; beforeAll(() => { project = gd.ProjectHelper.createNewGDJSProject(); layout = project.insertNewLayout('Scene', 0); layout .getObjects() .insertNewObject(project, 'Sprite', 'MySpriteObject', 0); initialInstance = layout.getInitialInstances().insertNewInitialInstance(); }); it('properties', function () { initialInstance.setObjectName('MySpriteObject'); expect(initialInstance.getObjectName()).toBe('MySpriteObject'); initialInstance.setX(150); expect(initialInstance.getX()).toBe(150); initialInstance.setY(140); expect(initialInstance.getY()).toBe(140); initialInstance.setAngle(45); expect(initialInstance.getAngle()).toBe(45); initialInstance.setZOrder(12); expect(initialInstance.getZOrder()).toBe(12); initialInstance.setLayer('MyLayer'); expect(initialInstance.getLayer()).toBe('MyLayer'); initialInstance.setLocked(true); expect(initialInstance.isLocked()).toBe(true); initialInstance.setHasCustomSize(true); expect(initialInstance.hasCustomSize()).toBe(true); initialInstance.setCustomWidth(34); expect(initialInstance.getCustomWidth()).toBe(34); initialInstance.setCustomHeight(30); expect(initialInstance.getCustomHeight()).toBe(30); expect(initialInstance.hasCustomDepth()).toBe(false); }); it('Sprite object custom properties', function () { initialInstance.updateCustomProperty( 'animation', '2', project.getObjects(), layout.getObjects() ); expect( initialInstance .getCustomProperties(project.getObjects(), layout.getObjects()) .get('animation') .getValue() ).toBe('2'); expect(initialInstance.getRawDoubleProperty('animation')).toBe(2); }); it('can be serialized', function () { expect(initialInstance.serializeTo).not.toBe(undefined); expect(initialInstance.unserializeFrom).not.toBe(undefined); let element = new gd.SerializerElement(); initialInstance.serializeTo(element); let initialInstance2 = layout .getInitialInstances() .insertNewInitialInstance(); initialInstance2.unserializeFrom(project, element); expect(initialInstance2.getObjectName()).toBe('MySpriteObject'); expect(initialInstance2.getX()).toBe(150); expect(initialInstance2.getY()).toBe(140); expect(initialInstance2.getZ()).toBe(0); expect(initialInstance2.getAngle()).toBe(45); expect(initialInstance2.getRotationX()).toBe(0); expect(initialInstance2.getRotationY()).toBe(0); expect(initialInstance2.getZOrder()).toBe(12); expect(initialInstance2.getLayer()).toBe('MyLayer'); expect(initialInstance2.isLocked()).toBe(true); expect(initialInstance2.hasCustomSize()).toBe(true); expect(initialInstance2.hasCustomDepth()).toBe(false); expect(initialInstance2.getCustomWidth()).toBe(34); expect(initialInstance2.getCustomHeight()).toBe(30); expect(initialInstance2.getCustomDepth()).toBe(0); }); it('can have 3D properties migrated from number properties', function () { const element = gd.Serializer.fromJSObject({ angle: 2, customSize: true, height: 100, layer: '', name: 'Walls', persistentUuid: '1075df65-af84-472d-a431-47d6f7a5cb63', width: 950, x: -100, y: -75, zOrder: 1, numberProperties: [ { name: 'depth', // This indicates there is a custom depth. value: 300, }, { name: 'z', value: 12, }, { name: 'rotationX', value: 1, }, { name: 'rotationY', value: 3, }, ], stringProperties: [], initialVariables: [], }); const migratedInstance = layout .getInitialInstances() .insertNewInitialInstance(); migratedInstance.unserializeFrom(project, element); element.delete(); expect(migratedInstance.getX()).toBe(-100); expect(migratedInstance.getY()).toBe(-75); expect(migratedInstance.getZ()).toBe(12); expect(migratedInstance.getAngle()).toBe(2); expect(migratedInstance.getRotationX()).toBe(1); expect(migratedInstance.getRotationY()).toBe(3); expect(migratedInstance.hasCustomSize()).toBe(true); expect(migratedInstance.hasCustomDepth()).toBe(true); expect(migratedInstance.getCustomWidth()).toBe(950); expect(migratedInstance.getCustomHeight()).toBe(100); expect(migratedInstance.getCustomDepth()).toBe(300); }); it('can have depth without a custom size', function () { const element = gd.Serializer.fromJSObject({ angle: 2, customSize: false, height: 100, layer: '', name: 'Walls', persistentUuid: '1075df65-af84-472d-a431-47d6f7a5cb63', width: 950, x: -100, y: -75, zOrder: 1, z: 12, depth: 300, // This indicates there is a custom depth. rotationX: 1, rotationY: 3, numberProperties: [], stringProperties: [], initialVariables: [], }); const instanceWithJustDepth = layout .getInitialInstances() .insertNewInitialInstance(); instanceWithJustDepth.unserializeFrom(project, element); element.delete(); expect(instanceWithJustDepth.getX()).toBe(-100); expect(instanceWithJustDepth.getY()).toBe(-75); expect(instanceWithJustDepth.getZ()).toBe(12); expect(instanceWithJustDepth.getAngle()).toBe(2); expect(instanceWithJustDepth.getRotationX()).toBe(1); expect(instanceWithJustDepth.getRotationY()).toBe(3); expect(instanceWithJustDepth.hasCustomSize()).toBe(false); expect(instanceWithJustDepth.hasCustomDepth()).toBe(true); expect(instanceWithJustDepth.getCustomDepth()).toBe(300); expect(instanceWithJustDepth.getCustomWidth()).toBe(950); expect(instanceWithJustDepth.getCustomHeight()).toBe(100); }); it('can have 3D properties', function () { const initialInstanceIn3D = layout .getInitialInstances() .insertNewInitialInstance(); initialInstanceIn3D.setX(40); initialInstanceIn3D.setY(41); initialInstanceIn3D.setZ(42); initialInstanceIn3D.setAngle(43); initialInstanceIn3D.setRotationX(44); initialInstanceIn3D.setRotationY(45); initialInstanceIn3D.setHasCustomSize(true); initialInstanceIn3D.setHasCustomDepth(true); initialInstanceIn3D.setCustomWidth(46); initialInstanceIn3D.setCustomHeight(47); initialInstanceIn3D.setCustomDepth(48); expect(initialInstanceIn3D.getX()).toBe(40); expect(initialInstanceIn3D.getY()).toBe(41); expect(initialInstanceIn3D.getZ()).toBe(42); expect(initialInstanceIn3D.getAngle()).toBe(43); expect(initialInstanceIn3D.getRotationX()).toBe(44); expect(initialInstanceIn3D.getRotationY()).toBe(45); expect(initialInstanceIn3D.hasCustomSize()).toBe(true); expect(initialInstanceIn3D.hasCustomDepth()).toBe(true); expect(initialInstanceIn3D.getCustomWidth()).toBe(46); expect(initialInstanceIn3D.getCustomHeight()).toBe(47); expect(initialInstanceIn3D.getCustomDepth()).toBe(48); const element = new gd.SerializerElement(); initialInstanceIn3D.serializeTo(element); const initialInstance2 = layout .getInitialInstances() .insertNewInitialInstance(); initialInstance2.unserializeFrom(project, element); expect(initialInstance2.getX()).toBe(40); expect(initialInstance2.getY()).toBe(41); expect(initialInstance2.getZ()).toBe(42); expect(initialInstance2.getAngle()).toBe(43); expect(initialInstance2.getRotationX()).toBe(44); expect(initialInstance2.getRotationY()).toBe(45); expect(initialInstance2.hasCustomSize()).toBe(true); expect(initialInstance2.hasCustomDepth()).toBe(true); expect(initialInstance2.getCustomWidth()).toBe(46); expect(initialInstance2.getCustomHeight()).toBe(47); expect(initialInstance2.getCustomDepth()).toBe(48); }); it('can be serialized with a persistent UUID called persistentUuid', function () { const initialInstance = new gd.InitialInstance(); initialInstance.setObjectName('MyObject'); // Serialized object must have a non empty "persistentUuid". let element = new gd.SerializerElement(); initialInstance.serializeTo(element); const persistentUuid = element.getStringAttribute('persistentUuid'); expect(persistentUuid).toBeTruthy(); // The UUID must persist across serializations. const initialInstance2 = new gd.InitialInstance(); initialInstance2.unserializeFrom(project, element); let element2 = new gd.SerializerElement(); initialInstance.serializeTo(element2); const persistentUuid2 = element2.getStringAttribute('persistentUuid'); expect(persistentUuid2).toBe(persistentUuid); // The UUID can be reset initialInstance2.resetPersistentUuid(); let element3 = new gd.SerializerElement(); initialInstance2.serializeTo(element3); const persistentUuid3 = element3.getStringAttribute('persistentUuid'); expect(persistentUuid3).not.toBe(persistentUuid2); element.delete(); element2.delete(); element3.delete(); initialInstance.delete(); initialInstance2.delete(); }); afterAll(function () { project.delete(); }); }); describe('gd.VariablesContainer', function () { it('container is empty after being created', function () { let container = new gd.VariablesContainer(); expect(container.has('Variable')).toBe(false); expect(container.count()).toBe(0); container.delete(); }); it('can insert variables', function () { let container = new gd.VariablesContainer(); container.insertNew('Variable', 0); expect(container.has('Variable')).toBe(true); expect(container.count()).toBe(1); container.insertNew('SecondVariable', 0); expect(container.has('SecondVariable')).toBe(true); expect(container.count()).toBe(2); container.delete(); }); it('can rename variables', function () { let container = new gd.VariablesContainer(); container.insertNew('Variable', 0); container .insertNew('SecondVariable', 0) .setString('String of SecondVariable'); container.insertNew('ThirdVariable', 0); expect(container.has('SecondVariable')).toBe(true); expect(container.has('NewName')).toBe(false); container.rename('SecondVariable', 'NewName'); expect(container.has('SecondVariable')).toBe(false); expect(container.has('NewName')).toBe(true); expect(container.get('NewName').getString()).toBe( 'String of SecondVariable' ); container.delete(); }); it('can swap variables', function () { let container = new gd.VariablesContainer(); container.insertNew('Variable', 0).setValue(4); container .insertNew('SecondVariable', 1) .setString('String of SecondVariable'); container.insertNew('ThirdVariable', 2).getChild('Child1').setValue(7); expect(container.getNameAt(0)).toBe('Variable'); expect(container.getNameAt(2)).toBe('ThirdVariable'); container.swap(0, 2); expect(container.getNameAt(0)).toBe('ThirdVariable'); expect(container.getNameAt(2)).toBe('Variable'); expect(container.getAt(2).getValue()).toBe(4); container.delete(); }); it('can move variables', function () { let container = new gd.VariablesContainer(); container.insertNew('Variable', 0).setValue(4); container .insertNew('SecondVariable', 1) .setString('String of SecondVariable'); container.insertNew('ThirdVariable', 2).getChild('Child1').setValue(7); container.move(1, 2); expect(container.getNameAt(0)).toBe('Variable'); expect(container.getNameAt(1)).toBe('ThirdVariable'); expect(container.getNameAt(2)).toBe('SecondVariable'); container.move(1, 9999); expect(container.getNameAt(0)).toBe('Variable'); expect(container.getNameAt(1)).toBe('ThirdVariable'); expect(container.getNameAt(2)).toBe('SecondVariable'); container.move(2, 0); expect(container.getNameAt(0)).toBe('SecondVariable'); expect(container.getNameAt(1)).toBe('Variable'); expect(container.getNameAt(2)).toBe('ThirdVariable'); container.delete(); }); }); describe('gd.Variable', function () { let variable = null; beforeEach(() => (variable = new gd.Variable())); it('should have initial value', function () { expect(variable.getValue()).toBe(0); expect(variable.getType()).toBe(gd.Variable.Number); }); it('can have a value', function () { variable.setValue(5); expect(variable.getValue()).toBe(5); expect(variable.getType()).toBe(gd.Variable.Number); }); it('can have a string value', function () { variable.setString('Hello'); expect(variable.getString()).toBe('Hello'); expect(variable.getType()).toBe(gd.Variable.String); }); it('can have a boolean value', function () { variable.setBool(true); expect(variable.getBool()).toBe(true); expect(variable.getType()).toBe(gd.Variable.Boolean); }); it('can be a structure', function () { variable.getChild('FirstChild').setValue(1); variable.getChild('SecondChild').setString('two'); expect(variable.hasChild('FirstChild')).toBe(true); expect(variable.hasChild('SecondChild')).toBe(true); expect(variable.hasChild('NotExisting')).toBe(false); expect(variable.getChild('SecondChild').getString()).toBe('two'); expect(variable.getType()).toBe(gd.Variable.Structure); variable.removeChild('FirstChild'); expect(variable.hasChild('FirstChild')).toBe(false); expect(variable.getType()).toBe(gd.Variable.Structure); variable.removeChild('SecondChild'); expect(variable.getType()).toBe(gd.Variable.Structure); }); it('can insert a child in structure', function () { variable.getChild('FirstChild').setValue(1); variable.getChild('SecondChild').setString('two'); expect(variable.getType()).toBe(gd.Variable.Structure); const variableToInsert = new gd.Variable(); variableToInsert.setString('strVariable'); { const result = variable.insertChild('FirstChild', variableToInsert); expect(result).toBe(false); } { const result = variable.insertChild('ThirdChild', variableToInsert); expect(result).toBe(true); } variableToInsert.delete(); expect(variable.hasChild('ThirdChild')).toBe(true); expect(variable.getChild('ThirdChild').getString()).toBe('strVariable'); }); it('can expose its children', function () { variable.getChild('FirstChild').setValue(1); variable.getChild('SecondChild').setValue(1); let childrenNames = variable.getAllChildrenNames(); expect(childrenNames.size()).toBe(2); variable.getChild(childrenNames.get(0)).setString('one'); variable.getChild(childrenNames.get(1)).setValue(2); expect(variable.getChild('FirstChild').getString()).toBe('one'); expect(variable.getChild('SecondChild').getValue()).toBe(2); expect(childrenNames.size()).toBe(2); }); it('can be an array', function () { variable.getAtIndex(0).setValue(1); expect(variable.getType()).toBe(gd.Variable.Array); variable.getAtIndex(2).setString('three'); expect(variable.getAllChildrenArray().size()).toBe(3); expect(variable.getAtIndex(0).getValue()).toBe(1); expect(variable.getAtIndex(1).getValue()).toBe(0); expect(variable.getAtIndex(2).getType()).toBe(gd.Variable.String); variable.removeAtIndex(2); expect(variable.getType()).toBe(gd.Variable.Array); variable.removeAtIndex(1); variable.removeAtIndex(0); expect(variable.getType()).toBe(gd.Variable.Array); }); it('can move children inside array', function () { variable.getAtIndex(0).setValue(1); expect(variable.getType()).toBe(gd.Variable.Array); variable.getAtIndex(1).setValue(2); variable.getAtIndex(2).setValue(3); variable.getAtIndex(3).setValue(4); variable.moveChildInArray(2, 0); expect(variable.getAtIndex(2).getValue()).toBe(2); expect(variable.getAtIndex(0).getValue()).toBe(3); }); it('can insert child in array', function () { variable.getAtIndex(0).setValue(1); expect(variable.getType()).toBe(gd.Variable.Array); variable.getAtIndex(1).setValue(2); variable.getAtIndex(2).setValue(3); variable.getAtIndex(3).setValue(4); const variableToInsert = new gd.Variable(); variableToInsert.setString('strVariable'); const result = variable.insertAtIndex(variableToInsert, 2); variableToInsert.delete(); expect(result).toBe(true); expect(variable.getAtIndex(2).getString()).toBe('strVariable'); expect(variable.getAtIndex(3).getValue()).toBe(3); expect(variable.getAtIndex(4).getValue()).toBe(4); }); it('can search inside children and remove them recursively', function () { let parentVariable = new gd.Variable(); let child1 = parentVariable.getChild('Child1'); let child2 = parentVariable.getChild('Child2'); let grandChild1 = parentVariable .getChild('Child1') .getChild('GrandChild'); let grandChild2 = parentVariable.getChild('Child2').getAtIndex(0); expect(parentVariable.contains(grandChild1, true)).toBe(true); expect(parentVariable.contains(grandChild1, false)).toBe(false); expect(parentVariable.contains(grandChild2, true)).toBe(true); expect(parentVariable.contains(grandChild2, false)).toBe(false); expect(parentVariable.contains(child1, true)).toBe(true); expect(parentVariable.contains(child2, true)).toBe(true); expect(parentVariable.contains(child1, false)).toBe(true); expect(parentVariable.contains(child2, false)).toBe(true); expect(child1.contains(grandChild1, true)).toBe(true); expect(child1.contains(grandChild1, false)).toBe(true); expect(child2.contains(grandChild1, true)).toBe(false); expect(child2.contains(grandChild1, false)).toBe(false); expect(child1.contains(grandChild2, true)).toBe(false); expect(child1.contains(grandChild2, false)).toBe(false); expect(child2.contains(grandChild2, true)).toBe(true); expect(child2.contains(grandChild2, false)).toBe(true); expect(grandChild1.contains(grandChild1, true)).toBe(false); expect(grandChild1.contains(grandChild1, false)).toBe(false); expect(grandChild1.contains(child1, true)).toBe(false); expect(grandChild1.contains(child2, true)).toBe(false); expect(grandChild1.contains(parentVariable, true)).toBe(false); expect(grandChild1.contains(child1, false)).toBe(false); expect(grandChild1.contains(child2, false)).toBe(false); expect(grandChild1.contains(parentVariable, false)).toBe(false); expect(grandChild2.contains(grandChild1, true)).toBe(false); expect(grandChild2.contains(grandChild1, false)).toBe(false); expect(grandChild2.contains(child1, true)).toBe(false); expect(grandChild2.contains(child2, true)).toBe(false); expect(grandChild2.contains(parentVariable, true)).toBe(false); expect(grandChild2.contains(child1, false)).toBe(false); expect(grandChild2.contains(child2, false)).toBe(false); expect(grandChild2.contains(parentVariable, false)).toBe(false); parentVariable.removeRecursively(grandChild1); expect(child1.hasChild('GrandChild')).toBe(false); expect(child1.getChildrenCount()).toBe(0); expect(parentVariable.getChildrenCount()).toBe(2); parentVariable.removeRecursively(grandChild2); expect(child2.getAllChildrenArray().size()).toBe(0); expect(child2.getChildrenCount()).toBe(0); expect(parentVariable.getChildrenCount()).toBe(2); parentVariable.removeRecursively(child2); expect(parentVariable.getChildrenCount()).toBe(1); expect(parentVariable.hasChild('Child1')).toBe(true); expect(parentVariable.hasChild('Child2')).toBe(false); parentVariable.delete(); }); afterAll(function () { variable.delete(); }); }); describe('gd.ImageResource', function () { it('should have name and file', function () { const resource = new gd.ImageResource(); resource.setName('MyResource'); resource.setFile('MyFile'); expect(resource.getName()).toBe('MyResource'); expect(resource.getFile()).toBe('MyFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.ImageResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); it('has smooth and alreadyLoaded custom properties', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const resource = new gd.ImageResource(); const properties = resource.getProperties(); expect(properties.get('Smooth the image').getValue()).toBe('true'); // Note: updateProperty expect the booleans in an usual "0" or "1" format. resource.updateProperty('Smooth the image', '0'); const updatedProperties = resource.getProperties(); expect(updatedProperties.get('Smooth the image').getValue()).toBe( 'false' ); resource.delete(); project.delete(); }); }); describe('gd.AudioResource', function () { it('should have name and file', function () { const resource = new gd.AudioResource(); resource.setName('MyAudioResource'); resource.setFile('MyAudioFile'); expect(resource.getName()).toBe('MyAudioResource'); expect(resource.getFile()).toBe('MyAudioFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.AudioResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); }); describe('gd.FontResource', function () { it('should have name and file', function () { const resource = new gd.FontResource(); resource.setName('MyFontResource'); resource.setFile('MyFontFile'); expect(resource.getName()).toBe('MyFontResource'); expect(resource.getFile()).toBe('MyFontFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.FontResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); }); describe('gd.BitmapFontResource', function () { it('should have name and file', function () { const resource = new gd.BitmapFontResource(); resource.setName('MyBitmapFontResource'); resource.setFile('MyBitmapFontFile'); expect(resource.getName()).toBe('MyBitmapFontResource'); expect(resource.getFile()).toBe('MyBitmapFontFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.BitmapFontResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); }); describe('gd.VideoResource', function () { it('should have name and file', function () { const resource = new gd.VideoResource(); resource.setName('MyVideoResource'); resource.setFile('MyVideoFile'); expect(resource.getName()).toBe('MyVideoResource'); expect(resource.getFile()).toBe('MyVideoFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.VideoResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); }); describe('gd.JavaScriptResource', function () { it('should have name and file', function () { const resource = new gd.JavaScriptResource(); resource.setName('MyJavaScriptResource'); resource.setFile('MyJavaScriptFile'); expect(resource.getName()).toBe('MyJavaScriptResource'); expect(resource.getFile()).toBe('MyJavaScriptFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.JavaScriptResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); }); describe('gd.JsonResource', function () { it('should have name and file', function () { const resource = new gd.JsonResource(); resource.setName('MyJsonResource'); resource.setFile('MyJsonFile'); expect(resource.getName()).toBe('MyJsonResource'); expect(resource.getFile()).toBe('MyJsonFile'); resource.delete(); }); it('can have metadata', function () { const resource = new gd.JsonResource(); expect(resource.getMetadata()).toBe(''); resource.setMetadata(JSON.stringify({ hello: 'world' })); expect(resource.getMetadata()).toBe('{"hello":"world"}'); resource.delete(); }); it('has disablePreload custom properties', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const resource = new gd.JsonResource(); const properties = resource.getProperties(); expect(properties.get('disablePreload').getValue()).toBe('false'); // Note: updateProperty expect the booleans in an usual "0" or "1" format. resource.updateProperty('disablePreload', '1'); const updatedProperties = resource.getProperties(); expect(updatedProperties.get('disablePreload').getValue()).toBe('true'); resource.delete(); project.delete(); }); }); describe('gd.ResourcesContainer', function () { it('should support adding resources', function () { let project = gd.ProjectHelper.createNewGDJSProject(); let resource = new gd.Resource(); let resource2 = new gd.Resource(); resource.setName('MyResource'); resource2.setName('MyResource2'); project.getResourcesManager().addResource(resource); project.getResourcesManager().addResource(resource2); let allResources = project.getResourcesManager().getAllResourceNames(); expect(allResources.size()).toBe(2); project.delete(); }); it('should support finding resources', function () { let project = gd.ProjectHelper.createNewGDJSProject(); let resource = new gd.Resource(); let resource2 = new gd.Resource(); resource.setName('MyResource'); resource2.setName('MyResource2'); project.getResourcesManager().addResource(resource); project.getResourcesManager().addResource(resource2); expect( project.getResourcesManager().getResourcePosition('MyResource') ).toBe(0); expect( project.getResourcesManager().getResourcePosition('MyResource2') ).toBe(1); expect( project.getResourcesManager().getResourcePosition('MyResource3') ).toBe(-1); project.delete(); }); it('can find files that are not in the resources', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const resource = new gd.ImageResource(); const resource2 = new gd.AudioResource(); const resource3 = new gd.AudioResource(); const resource4 = new gd.JsonResource(); resource.setName('MyResource'); resource.setFile('MyFile.png'); resource2.setName('MyResource2'); resource2.setFile('MySubFolder/MyOtherFile.mp3'); resource3.setName('MyResource3'); resource3.setFile('MySubFolder\\MyOtherFileInWindowsFormat.mp3'); resource4.setName('MyResource4'); resource4.setFile('MySubFolder/MyOtherFileInUnixFormat.json'); project.getResourcesManager().addResource(resource); project.getResourcesManager().addResource(resource2); project.getResourcesManager().addResource(resource3); project.getResourcesManager().addResource(resource4); const filesToCheck = new gd.VectorString(); [ 'MyFile.png', 'MySubFolder/MyFileMissingInResource.png', 'MySubFolder/MyOtherFile.mp3', // Check that resources can be found even if specified using // a different path separator: 'MySubFolder/MyOtherFileInWindowsFormat.mp3', 'MySubFolder\\MyOtherFileInUnixFormat.json', ].forEach((filePath) => { filesToCheck.push_back(filePath); }); const filesNotInResources = project .getResourcesManager() .findFilesNotInResources(filesToCheck); filesToCheck.delete(); expect(filesNotInResources.size()).toBe(1); expect(filesNotInResources.at(0)).toBe( 'MySubFolder/MyFileMissingInResource.png' ); project.delete(); }); it('should support removing resources', function () { let project = gd.ProjectHelper.createNewGDJSProject(); let resource = new gd.Resource(); resource.setName('MyResource'); project.getResourcesManager().addResource(resource); project.getResourcesManager().removeResource('MyResource'); let allResources = project.getResourcesManager().getAllResourceNames(); expect(allResources.size()).toBe(0); project.delete(); }); }); describe('gd.ProjectResourcesAdder', function () { it('should support removing useless resources', function () { let project = gd.ProjectHelper.createNewGDJSProject(); let resource1 = new gd.ImageResource(); resource1.setName('Useless'); let resource2 = new gd.ImageResource(); resource2.setName('Used'); project.getResourcesManager().addResource(resource1); project.getResourcesManager().addResource(resource2); //Create an object using a resource let obj = project .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); let sprite1 = new gd.Sprite(); sprite1.setImageName('Used'); let anim1 = new gd.Animation(); anim1.setDirectionsCount(1); anim1.getDirection(0).addSprite(sprite1); gd.castObject(obj.getConfiguration(), gd.SpriteObject) .getAnimations() .addAnimation(anim1); { let allResources = project.getResourcesManager().getAllResourceNames(); expect(allResources.size()).toBe(2); } gd.ProjectResourcesAdder.removeAllUseless(project, 'image'); { let allResources = project.getResourcesManager().getAllResourceNames(); expect(allResources.size()).toBe(1); expect(allResources.get(0)).toBe('Used'); } project.delete(); }); it('should not remove loading screen image when removing useless resources', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const resource = new gd.ImageResource(); resource.setName('LoadingScreenImage'); project .getLoadingScreen() .setBackgroundImageResourceName(resource.getName()); expect(project.getLoadingScreen().getBackgroundImageResourceName()).toBe( 'LoadingScreenImage' ); const worker = new gd.ResourcesInUseHelper(project.getResourcesManager()); gd.ResourceExposer.exposeWholeProjectResources(project, worker); expect(worker.getAllImages().toNewVectorString().toJSArray().length).toBe( 1 ); expect(worker.getAllImages().toNewVectorString().toJSArray()[0]).toBe( 'LoadingScreenImage' ); gd.ProjectResourcesAdder.removeAllUseless(project, 'image'); const newWorker = new gd.ResourcesInUseHelper( project.getResourcesManager() ); gd.ResourceExposer.exposeWholeProjectResources(project, newWorker); expect( newWorker.getAllImages().toNewVectorString().toJSArray().length ).toBe(1); expect(newWorker.getAllImages().toNewVectorString().toJSArray()[0]).toBe( 'LoadingScreenImage' ); project.delete(); }); }); describe('gd.ArbitraryResourceWorker', function () { it('should be called with resources of the project', function (done) { let project = gd.ProjectHelper.createNewGDJSProject(); let obj = project .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); const spriteConfiguration = gd.asSpriteConfiguration( obj.getConfiguration() ); let sprite1 = new gd.Sprite(); sprite1.setImageName('Used'); const animation = new gd.Animation(); animation.setDirectionsCount(1); animation.getDirection(0).addSprite(sprite1); spriteConfiguration.getAnimations().addAnimation(animation); let worker = extend( new gd.ArbitraryResourceWorkerJS(project.getResourcesManager()), { exposeImage: function (image) { expect(image).toBe('Used'); done(); return image; }, } ); gd.ResourceExposer.exposeWholeProjectResources(project, worker); project.delete(); }); }); describe('gd.ResourcesInUseHelper', function () { it('should find the images used by objects', function () { const project = gd.ProjectHelper.createNewGDJSProject(); let sprite1 = new gd.Sprite(); sprite1.setImageName('Image1'); let sprite2 = new gd.Sprite(); sprite2.setImageName('Image2'); let sprite3 = new gd.Sprite(); sprite3.setImageName('Image3'); const spriteObject = new gd.SpriteObject(); const animation = new gd.Animation(); animation.setDirectionsCount(1); animation.getDirection(0).addSprite(sprite1); animation.getDirection(0).addSprite(sprite2); animation.getDirection(0).addSprite(sprite1); spriteObject.getAnimations().addAnimation(animation); const spriteObject2 = new gd.SpriteObject(); const animation2 = new gd.Animation(); animation2.setDirectionsCount(1); animation2.getDirection(0).addSprite(sprite1); animation2.getDirection(0).addSprite(sprite3); animation2.getDirection(0).addSprite(sprite1); spriteObject2.getAnimations().addAnimation(animation2); const resourcesInUse = new gd.ResourcesInUseHelper( project.getResourcesManager() ); { spriteObject.exposeResources(resourcesInUse); const resourceNames = resourcesInUse .getAllImages() .toNewVectorString() .toJSArray(); expect(resourceNames).toHaveLength(2); expect(resourceNames).toContain('Image1'); expect(resourceNames).toContain('Image2'); } { spriteObject2.exposeResources(resourcesInUse); const resourceNames = resourcesInUse .getAllImages() .toNewVectorString() .toJSArray(); expect(resourceNames).toHaveLength(3); expect(resourceNames).toContain('Image1'); expect(resourceNames).toContain('Image2'); expect(resourceNames).toContain('Image3'); } resourcesInUse.delete(); project.delete(); spriteObject.delete(); spriteObject2.delete(); }); }); describe('gd.ObjectsUsingResourceCollector', function () { it('lists objects that use the given resources', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const layout = project.insertNewLayout('Scene', 0); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); const sprite1 = new gd.Sprite(); sprite1.setImageName('Image1'); const sprite2 = new gd.Sprite(); sprite2.setImageName('Image2'); const sprite3 = new gd.Sprite(); sprite3.setImageName('Image3'); const spriteObject = gd.asSpriteConfiguration(object.getConfiguration()); const animation = new gd.Animation(); animation.setDirectionsCount(1); animation.getDirection(0).addSprite(sprite1); animation.getDirection(0).addSprite(sprite2); animation.getDirection(0).addSprite(sprite1); spriteObject.getAnimations().addAnimation(animation); const object2 = project .getObjects() .insertNewObject(project, 'Sprite', 'MyObject2', 0); const spriteObject2 = gd.asSpriteConfiguration( object2.getConfiguration() ); const animation2 = new gd.Animation(); animation2.setDirectionsCount(1); animation2.getDirection(0).addSprite(sprite1); animation2.getDirection(0).addSprite(sprite3); animation2.getDirection(0).addSprite(sprite1); spriteObject2.getAnimations().addAnimation(animation2); { const objectsCollector = new gd.ObjectsUsingResourceCollector( project.getResourcesManager(), 'Image1' ); gd.ProjectBrowserHelper.exposeProjectObjects(project, objectsCollector); const objectNames = objectsCollector.getObjectNames().toJSArray(); objectsCollector.delete(); expect(objectNames.length).toEqual(2); expect(objectNames).toContain('MyObject'); expect(objectNames).toContain('MyObject2'); } { const objectsCollector = new gd.ObjectsUsingResourceCollector( project.getResourcesManager(), 'Image2' ); gd.ProjectBrowserHelper.exposeProjectObjects(project, objectsCollector); const objectNames = objectsCollector.getObjectNames().toJSArray(); objectsCollector.delete(); expect(objectNames.length).toEqual(1); expect(objectNames).toContain('MyObject'); } { const objectsCollector = new gd.ObjectsUsingResourceCollector( project.getResourcesManager(), 'Image3' ); gd.ProjectBrowserHelper.exposeProjectObjects(project, objectsCollector); const objectNames = objectsCollector.getObjectNames().toJSArray(); objectsCollector.delete(); expect(objectNames.length).toEqual(1); expect(objectNames).toContain('MyObject2'); } project.delete(); }); }); describe('gd.Behavior', function () { it('update a not existing property', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const behavior = new gd.Behavior(); const serializerElement = new gd.SerializerElement(); expect( behavior.updateProperty( serializerElement, 'PropertyThatDoesNotExist', 'MyValue' ) ).toBe(false); serializerElement.delete(); behavior.delete(); project.delete(); }); }); describe('gd.BehaviorsSharedData', function () { it('can be created by gd.Layout.updateBehaviorsSharedData', function () { let project = gd.ProjectHelper.createNewGDJSProject(); let layout = project.insertNewLayout('Scene', 0); let object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); layout.updateBehaviorsSharedData(project); expect(layout.hasBehaviorSharedData('Physics')).toBe(false); let behaviorContent = object.addNewBehavior( project, 'PhysicsBehavior::PhysicsBehavior', 'Physics' ); expect(layout.hasBehaviorSharedData('Physics')).toBe(false); layout.updateBehaviorsSharedData(project); expect(layout.hasBehaviorSharedData('Physics')).toBe(true); layout.getObjects().removeObject('MyObject'); expect(layout.hasBehaviorSharedData('Physics')).toBe(true); layout.updateBehaviorsSharedData(project); expect(layout.hasBehaviorSharedData('Physics')).toBe(false); project.delete(); }); }); describe('gd.BehaviorSharedDataJsImplementation', function () { it('can declare a gd.BehaviorSharedDataJsImplementation and pass sanity checks', function () { let mySharedData = new gd.BehaviorSharedDataJsImplementation(); mySharedData.updateProperty = function ( behaviorContent, propertyName, newValue ) { if (propertyName === 'My first property') { behaviorContent.setStringAttribute('property1', newValue); return true; } if (propertyName === 'My other property') { behaviorContent.setBoolAttribute('property2', newValue === '1'); return true; } return false; }; mySharedData.getProperties = function (behaviorContent) { let properties = new gd.MapStringPropertyDescriptor(); properties .getOrCreate('My first property') .setValue(behaviorContent.getStringAttribute('property1')); properties .getOrCreate('My other property') .setValue(behaviorContent.getBoolAttribute('property2') ? '1' : '0') .setType('Boolean'); return properties; }; mySharedData.initializeContent = function (behaviorContent) { behaviorContent.setStringAttribute('property1', 'Initial value 1'); behaviorContent.setBoolAttribute('property2', true); }; try { expect( gd.ProjectHelper.sanityCheckBehaviorsSharedDataProperty( mySharedData, 'My first property', 'Value1' ) ).toBe(''); expect( gd.ProjectHelper.sanityCheckBehaviorsSharedDataProperty( mySharedData, 'My other property', '0' ) ).toBe(''); } catch (ex) { console.error(ex); throw Error( 'Exception caught while launching sanityCheckBehavior on a gd.BehaviorSharedDataJsImplementation.' ); } }); }); describe('gd.NamedPropertyDescriptor', function () { const makeNewProperty = () => { const property = new gd.NamedPropertyDescriptor(); property .setName('Property1') .setLabel('The first property') .setValue('Hello world') .setType('string') .addExtraInfo('Info1') .addExtraInfo('Info2'); return property; }; it('can be created and manipulated', function () { const property = makeNewProperty(); expect(property.getName()).toBe('Property1'); expect(property.getLabel()).toBe('The first property'); expect(property.getValue()).toBe('Hello world'); expect(property.getType()).toBe('string'); expect(property.getExtraInfo().toJSArray()).toContain('Info1'); expect(property.getExtraInfo().toJSArray()).toContain('Info2'); property.delete(); }); it('can be serialized', function () { const property = makeNewProperty(); let serializerElement = new gd.SerializerElement(); property.serializeTo(serializerElement); property.delete(); const property2 = new gd.NamedPropertyDescriptor(); property2.unserializeFrom(serializerElement); serializerElement.delete(); expect(property2.getName()).toBe('Property1'); expect(property2.getLabel()).toBe('The first property'); expect(property2.getValue()).toBe('Hello world'); expect(property2.getType()).toBe('string'); expect(property2.getExtraInfo().toJSArray()).toContain('Info1'); expect(property2.getExtraInfo().toJSArray()).toContain('Info2'); }); }); describe('gd.PropertiesContainer', function () { it('can be used to store named properties', function () { const list = new gd.PropertiesContainer(0); const property1 = list.insertNew('Property1', 0); expect(list.has('Property1')).toBe(true); expect(list.getCount()).toBe(1); expect(property1.getName()).toBe('Property1'); expect(list.getAt(0).getName()).toBe('Property1'); property1.setLabel('Property 1'); property1.setValue('123'); expect(list.getAt(0).getLabel()).toBe('Property 1'); expect(list.getAt(0).getValue()).toBe('123'); list.delete(); }); }); describe('gd.MapStringPropertyDescriptor', function () { it('can be used to manipulate properties', function () { let properties = new gd.MapStringPropertyDescriptor(); expect(properties.has('Property0')).toBe(false); // Ensure the "set" method works (though in practice, prefer "getOrCreate") const property1 = new gd.PropertyDescriptor('Hello Property1'); properties.set('Property1', property1); property1.delete(); expect(properties.get('Property1').getValue()).toBe('Hello Property1'); expect(properties.get('Property1').getType()).toBe('string'); properties.get('Property1').setValue('Hello modified Property1'); expect(properties.get('Property1').getValue()).toBe( 'Hello modified Property1' ); expect(properties.keys().toJSArray()).not.toContain('Property0'); expect(properties.keys().toJSArray()).toContain('Property1'); // Ensure the "getOrCreate" method works expect(properties.has('Property0')).toBe(false); properties .getOrCreate('Property0') .setValue('Hello Property0') .setType('another type') .addExtraInfo('Info1') .addExtraInfo('Info3'); expect(properties.has('Property0')).toBe(true); expect(properties.get('Property0').getValue()).toBe('Hello Property0'); expect(properties.get('Property0').getType()).toBe('another type'); expect(properties.get('Property0').getExtraInfo().toJSArray()).toContain( 'Info1' ); expect( properties.get('Property0').getExtraInfo().toJSArray() ).not.toContain('Info2'); expect(properties.get('Property0').getExtraInfo().toJSArray()).toContain( 'Info3' ); expect(properties.has('Property0')).toBe(true); expect(properties.has('Property1')).toBe(true); expect(properties.keys().toJSArray()).toContain('Property0'); expect(properties.keys().toJSArray()).toContain('Property1'); properties.delete(); }); }); describe('gd.BehaviorJsImplementation', function () { it('can declare a gd.BehaviorJsImplementation and pass sanity checks', function () { let myBehavior = new gd.BehaviorJsImplementation(); myBehavior.updateProperty = function ( behaviorContent, propertyName, newValue ) { if (propertyName === 'My first property') { behaviorContent.setStringAttribute('property1', newValue); return true; } if (propertyName === 'My other property') { behaviorContent.setBoolAttribute('property2', newValue === '1'); return true; } return false; }; myBehavior.getProperties = function (behaviorContent) { let properties = new gd.MapStringPropertyDescriptor(); properties .getOrCreate('My first property') .setValue(behaviorContent.getStringAttribute('property1')); properties .getOrCreate('My other property') .setValue(behaviorContent.getBoolAttribute('property2') ? '1' : '0') .setType('Boolean'); return properties; }; myBehavior.initializeContent = function (behaviorContent) { behaviorContent.setStringAttribute('property1', 'Initial value 1'); behaviorContent.setBoolAttribute('property2', true); }; try { expect( gd.ProjectHelper.sanityCheckBehaviorProperty( myBehavior, 'My first property', 'Value1' ) ).toBe(''); expect( gd.ProjectHelper.sanityCheckBehaviorProperty( myBehavior, 'My other property', '0' ) ).toBe(''); } catch (ex) { console.error(ex); throw Error( 'Exception caught while launching sanityCheckBehavior on a gd.BehaviorJsImplementation.' ); } }); }); describe('gd.Object', function () { let project = null; let layout = null; let object = null; let object2 = null; beforeAll(() => { project = gd.ProjectHelper.createNewGDJSProject(); layout = project.insertNewLayout('Scene', 0); object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); object2 = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject2', 1); }); it('has properties and initial values', function () { object.setName('TheObject'); expect(object.getName()).toBe('TheObject'); expect(object.hasBehaviorNamed('DoNotExists')).toBe(false); }); it('can have its type retrieved with gd.getTypeOfObject', function () { expect( gd.getTypeOfObject( project.getObjects(), layout.getObjects(), 'TheObject', true ) ).toBe('Sprite'); }); it('can have behaviors', function () { let behavior = object.addNewBehavior( project, 'DraggableBehavior::Draggable', 'Draggable' ); expect(object.hasBehaviorNamed('Draggable')).toBe(true); expect(object.getBehavior('Draggable')).toBe(behavior); }); const spriteDefaultBehaviorCount = 6; it('can have its behaviors retrieved with gd.getBehaviorsOfObject', function () { let behaviors = gd.getBehaviorsOfObject( project.getObjects(), layout.getObjects(), 'TheObject', true ); expect(behaviors.size()).toBe(1 + spriteDefaultBehaviorCount); expect(behaviors.get(1)).toBe('Draggable'); }); it('can be un/serialized (basic)', function () { let serializerElement = new gd.SerializerElement(); object.serializeTo(serializerElement); object2.unserializeFrom(project, serializerElement); object2.unserializeFrom(project, serializerElement); // Also check that multiple object2.unserializeFrom(project, serializerElement); // unserialization is idempotent serializerElement.delete(); //Check that behaviors were persisted and restored let behaviors = object2.getAllBehaviorNames(); expect(behaviors.size()).toBe(1 + spriteDefaultBehaviorCount); expect(behaviors.at(1)).toBe('Draggable'); }); it('can be un/serialized (with behavior content)', function () { const behaviorContent = object.getBehavior('Draggable'); behaviorContent.updateProperty('checkCollisionMask', 'true'); let serializerElement = new gd.SerializerElement(); object.serializeTo(serializerElement); object2.unserializeFrom(project, serializerElement); object2.unserializeFrom(project, serializerElement); // Also check that multiple object2.unserializeFrom(project, serializerElement); // unserialization is idempotent serializerElement.delete(); //Check that behaviors were persisted and restored let behaviors = object2.getAllBehaviorNames(); expect(behaviors.size()).toBe(1 + spriteDefaultBehaviorCount); expect(behaviors.at(1)).toBe('Draggable'); const behaviorContent2 = object2.getBehavior('Draggable'); expect( behaviorContent2.getProperties().get('checkCollisionMask').getValue() ).toBe('true'); }); afterAll(function () { project.delete(); }); }); describe('gd.ObjectJsImplementation', function () { const createSampleObjectJsImplementation = () => { let myObject = new gd.ObjectJsImplementation(); myObject.updateProperty = function (propertyName, newValue) { if (propertyName === 'My first property') { this.content.property1 = newValue; return true; } if (propertyName === 'My other property') { this.content.property2 = newValue === '1'; return true; } return false; }; myObject.getProperties = function () { let properties = new gd.MapStringPropertyDescriptor(); properties .getOrCreate('My first property') .setValue(this.content.property1); properties .getOrCreate('My other property') .setValue(this.content.property2 ? '1' : '0') .setType('Boolean'); return properties; }; myObject.content = { property1: 'Initial value 1', property2: true, }; myObject.updateInitialInstanceProperty = function ( instance, propertyName, newValue ) { if (propertyName === 'My instance property') { instance.setRawStringProperty('instanceprop1', newValue); return true; } if (propertyName === 'My other instance property') { instance.setRawDoubleProperty('instanceprop2', parseFloat(newValue)); return true; } return false; }; myObject.getInitialInstanceProperties = function (instance) { let properties = new gd.MapStringPropertyDescriptor(); properties .getOrCreate('My instance property') .setValue(instance.getRawStringProperty('instanceprop1')); properties .getOrCreate('My other instance property') .setValue(instance.getRawDoubleProperty('instanceprop2').toString()) .setType('number'); return properties; }; // Cast the object to a base gdObjectConfiguration to ensure C++ methods are called, // and verify they properly call their JS equivalents. return gd.castObject(myObject, gd.ObjectConfiguration); }; it('can create a gd.ObjectJsImplementation and pass sanity checks', function () { const myObject = createSampleObjectJsImplementation(); try { expect( gd.ProjectHelper.sanityCheckObjectProperty( myObject, 'My first property', 'Value1' ) ).toBe(''); expect( gd.ProjectHelper.sanityCheckObjectProperty( myObject, 'My other property', '0' ) ).toBe(''); expect( gd.ProjectHelper.sanityCheckObjectInitialInstanceProperty( myObject, 'My instance property', 'Value1' ) ).toBe(''); expect( gd.ProjectHelper.sanityCheckObjectInitialInstanceProperty( myObject, 'My other instance property', '0' ) ).toBe(''); } catch (ex) { console.error(ex); throw Error( 'Exception caught while launching sanityCheckObject on a gd.ObjectJsImplementation.' ); } }); it('can clone a gd.ObjectJsImplementation', function () { const object1 = createSampleObjectJsImplementation(); expect( object1.getProperties().get('My first property').getValue() == 'Initial value 1' ); object1.updateProperty('My first property', 'test1'); const object2 = object1.clone().release(); const object3 = object1.clone().release(); const object1jsImplementation = gd.castObject( object1, gd.ObjectJsImplementation ); const object2jsImplementation = gd.castObject( object2, gd.ObjectJsImplementation ); const object3jsImplementation = gd.castObject( object3, gd.ObjectJsImplementation ); { // Check properties can be accessed. const propertiesObject1 = object1.getProperties(); expect(propertiesObject1.has('My first property')); expect( propertiesObject1.get('My first property').getValue() == 'test1' ); const propertiesObject2 = object2.getProperties(); expect(propertiesObject2.has('My first property')); expect( propertiesObject2.get('My first property').getValue() == 'test1' ); // Check the JavaScript objects are unchanged for now. expect(object1jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); expect(object2jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); expect(object3jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); } { // Check a property can be updated. object1.updateProperty('My first property', 'updated value'); const propertiesObject1 = object1.getProperties(); expect(propertiesObject1.has('My first property')); expect( propertiesObject1.get('My first property').getValue() == 'updated value' ); const propertiesObject2 = object2.getProperties(); expect(propertiesObject2.has('My first property')); expect( propertiesObject2.get('My first property').getValue() == 'test1' ); // Check the JavaScript objects are updated. expect(object1jsImplementation.content).toEqual({ property1: 'updated value', property2: true, }); expect(object2jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); expect(object3jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); } { // Check a property from another object can be updated. object2.updateProperty('My first property', 'updated value object 2'); const propertiesObject1 = object1.getProperties(); expect(propertiesObject1.has('My first property')); expect( propertiesObject1.get('My first property').getValue() == 'updated value' ); const propertiesObject2 = object2.getProperties(); expect(propertiesObject2.has('My first property')); expect( propertiesObject2.get('My first property').getValue() == 'updated value object 2' ); const propertiesObject3 = object3.getProperties(); expect(propertiesObject3.has('My first property')); expect( propertiesObject3.get('My first property').getValue() == 'test1' ); // Check the JavaScript objects are updated. expect(object1jsImplementation.content).toEqual({ property1: 'updated value', property2: true, }); expect(object2jsImplementation.content).toEqual({ property1: 'updated value object 2', property2: true, }); expect(object3jsImplementation.content).toEqual({ property1: 'test1', property2: true, }); } }); }); describe('gd.ObjectGroupsContainer', function () { let container = null; let group1 = null; let group2 = null; let group3 = null; beforeAll(() => (container = new gd.ObjectGroupsContainer())); it('can have groups inserted', function () { group1 = container.insertNew('Group1', 0); group2 = container.insertNew('Group2', 1); group3 = container.insertNew('Group3', 2); expect(container.getAt(0).getName()).toBe('Group1'); expect(container.getAt(1).getName()).toBe('Group2'); expect(container.getAt(2).getName()).toBe('Group3'); expect(container.has('Group1')).toBe(true); expect(container.has('Group2')).toBe(true); expect(container.has('Group3')).toBe(true); expect(container.has('Group4')).toBe(false); expect(container.get('Group1').getName()).toBe('Group1'); expect(container.get('Group2').getName()).toBe('Group2'); expect(container.get('Group3').getName()).toBe('Group3'); expect(container.count()).toBe(3); }); it('can move groups', function () { container.move(0, 1); expect(container.getAt(0).getName()).toBe('Group2'); expect(container.getAt(1).getName()).toBe('Group1'); expect(container.getAt(2).getName()).toBe('Group3'); }); it('can rename groups', function () { container.rename('Inexisting', 'Whatever'); container.rename('Group1', 'Group1Renamed'); expect(container.has('Group1')).toBe(false); expect(container.has('Group1Renamed')).toBe(true); }); it('can remove groups', function () { container.remove('Group2'); expect(container.has('Group2')).toBe(false); expect(container.has('Group3')).toBe(true); expect(container.count()).toBe(2); }); }); describe('gd.Instruction', function () { it('initial values', function () { let instr = new gd.Instruction(); expect(instr.getParametersCount()).toBe(0); expect(instr.getSubInstructions().size()).toBe(0); instr.delete(); }); it('setting parameters', function () { let instr = new gd.Instruction(); instr.setParametersCount(3); expect(instr.getParametersCount()).toBe(3); expect(instr.getParameter(1).getPlainString()).toBe(''); instr.setParameter(2, 'MyValue'); expect(instr.getParameter(2).getPlainString()).toBe('MyValue'); instr.delete(); }); it('can be cloned', function () { let instr = new gd.Instruction(); instr.setParametersCount(3); instr.setParameter(2, 'MyValue'); let newInstr = instr.clone(); expect(newInstr.getParametersCount()).toBe(3); expect(newInstr.getParameter(1).getPlainString()).toBe(''); expect(newInstr.getParameter(2).getPlainString()).toBe('MyValue'); newInstr.setParameter(2, 'MyChangedValue'); expect(instr.getParameter(2).getPlainString()).toBe('MyValue'); expect(newInstr.getParameter(2).getPlainString()).toBe('MyChangedValue'); newInstr.delete(); expect(instr.getParameter(2).getPlainString()).toBe('MyValue'); instr.delete(); }); }); describe('gd.InstructionsList', function () { let list = null; beforeAll(() => (list = new gd.InstructionsList())); it('can insert instructions', function () { expect(list.size()).toBe(0); list.insert(new gd.Instruction(), 0); expect(list.size()).toBe(1); }); it('can modify its instructions', function () { expect(list.get(0).getType()).toBe(''); let newInstr = new gd.Instruction(); newInstr.setType('Type2'); list.set(0, newInstr); expect(list.get(0).getType()).toBe('Type2'); expect(list.size()).toBe(1); }); it('can remove its instructions', function () { let newInstr = new gd.Instruction(); newInstr.setType('Type3'); let instruction = list.insert(newInstr, 1); expect(list.get(1).getType()).toBe('Type3'); expect(list.size()).toBe(2); expect(list.contains(instruction)).toBe(true); list.remove(instruction); expect(list.size()).toBe(1); expect(list.get(0).getType()).toBe('Type2'); }); it('can clear its instructions', function () { list.clear(); expect(list.size()).toBe(0); }); it('can insert events from another list', function () { let list1 = new gd.InstructionsList(); let list2 = new gd.InstructionsList(); let newInstr = new gd.Instruction(); newInstr.setType('Type1'); list1.insert(newInstr, 0); let newInstr2 = new gd.Instruction(); newInstr2.setType('Type2'); list1.insert(newInstr2, 1); list2.insertInstructions(list1, 0, list1.size(), 0); expect(list2.size()).toBe(2); expect(list2.get(0).getType()).toBe('Type1'); expect(list2.get(1).getType()).toBe('Type2'); list2.insertInstructions(list1, 0, list1.size(), 1); expect(list2.size()).toBe(4); expect(list2.get(0).getType()).toBe('Type1'); expect(list2.get(1).getType()).toBe('Type1'); expect(list2.get(2).getType()).toBe('Type2'); expect(list2.get(3).getType()).toBe('Type2'); list1.delete(); list2.delete(); }); it('can be un/serialized', function () { let newInstr = new gd.Instruction(); newInstr.setType('Type1'); newInstr.setParametersCount(2); newInstr.setParameter(0, 'Param1'); newInstr.setParameter(1, 'Param2'); let instruction = list.insert(newInstr, 1); let newInstr2 = new gd.Instruction(); newInstr2.setType('Type2'); newInstr2.setParametersCount(1); newInstr2.setParameter(0, 'Param3'); let instruction2 = list.insert(newInstr2, 1); let project = gd.ProjectHelper.createNewGDJSProject(); let serializerElement = new gd.SerializerElement(); list.serializeTo(serializerElement); let list2 = new gd.InstructionsList(); list2.unserializeFrom(project, serializerElement); expect(list2.size()).toBe(2); expect(list2.get(0).getType()).toBe('Type1'); expect(list2.get(1).getType()).toBe('Type2'); expect(list2.get(0).getParametersCount()).toBe(2); expect(list2.get(1).getParametersCount()).toBe(1); expect(list2.get(0).getParameter(0).getPlainString()).toBe('Param1'); expect(list2.get(0).getParameter(1).getPlainString()).toBe('Param2'); expect(list2.get(1).getParameter(0).getPlainString()).toBe('Param3'); list2.delete(); project.delete(); }); afterAll(function () { list.delete(); }); }); describe('InstructionSentenceFormatter', function () { it('should translate instructions (plain text or into a vector of text with formatting)', function () { let action = new gd.Instruction(); //Create a simple instruction action.setType('Delete'); action.setParametersCount(2); action.setParameter(0, 'MyCharacter'); let formattedTexts = gd.InstructionSentenceFormatter.get().getAsFormattedText( action, gd.MetadataProvider.getActionMetadata(gd.JsPlatform.get(), 'Delete') ); expect(formattedTexts.size()).toBe(2); expect(formattedTexts.getString(0)).toBe('Delete '); expect(formattedTexts.getString(1)).toBe('MyCharacter'); expect(formattedTexts.getTextFormatting(0).getUserData()).not.toBe(0); expect(formattedTexts.getTextFormatting(1).getUserData()).toBe(0); action.delete(); }); }); describe('EventsRefactorer', function () { describe('SearchInEvents', function () { let eventList = null; let event1 = null; let event2 = null; beforeAll(() => { eventList = new gd.EventsList(); /* Event 1 */ { const event = new gd.StandardEvent(); let eventActions1 = event.getActions(); let action1 = new gd.Instruction(); action1.setType('RotateTowardPosition'); // should generate the sentence `Rotate _PARAM0_ towards _PARAM1_;_PARAM2_ at speed _PARAM3_deg/second` action1.setParametersCount(4); action1.setParameter(0, 'Platform'); action1.setParameter(1, '450'); action1.setParameter(2, '200'); action1.setParameter(3, '90'); eventActions1.push_back(action1); let eventConditions1 = event.getConditions(); let condition1 = new gd.Instruction(); condition1.setType('PosX'); // should generate the sentence `the X position of _PARAM0_ _PARAM1_ _PARAM2_` condition1.setParametersCount(3); condition1.setParameter(0, 'MyCharacter'); condition1.setParameter(1, '<'); condition1.setParameter(2, '300'); eventConditions1.push_back(condition1); event1 = eventList.insertEvent(event, 0); action1.delete(); condition1.delete(); } /* Event 2 */ { const event = new gd.StandardEvent(); let eventActions2 = event.getActions(); let action2 = new gd.Instruction(); action2.setType('Delete'); // should generate the sentence `Delete _PARAM0_` action2.setParametersCount(1); action2.setParameter(0, 'OtherCharacter'); eventActions2.push_back(action2); let eventConditions2 = event.getConditions(); let condition2 = new gd.Instruction(); condition2.setType('Angle'); // should generate the sentence `the angle (in degrees) of _PARAM0_ _PARAM1_ _PARAM2_` condition2.setParametersCount(3); condition2.setParameter(0, 'OtherPlatform'); condition2.setParameter(1, '>'); condition2.setParameter(2, '55'); eventConditions2.push_back(condition2); event2 = eventList.insertEvent(event, 0); action2.delete(); condition2.delete(); } }); afterAll(() => { eventList.delete(); }); it('should search string in parameters only and respect case', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'mycharacter', true, true, true, false, false, false ); expect(searchResultEvents1.size()).toBe(0); const searchResultEvents2 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'MyCharacter', true, true, true, false, false, false ); expect(searchResultEvents2.size()).toBe(1); expect(searchResultEvents2.at(0).getEvent()).toBe(event1); }); it('should search string in parameters only', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'mycharacter', false, true, true, false, false, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event1); const searchResultEvents2 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'position', false, true, true, false, false, false ); expect(searchResultEvents2.size()).toBe(0); }); it('should search string in sentences', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'In Degrees', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event2); }); it('should search string in sentences with parameter placeholders replaced', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'position of MyCharacter', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event1); }); it('should search string in sentences with parameter placeholders replaced and special characters removed', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'towards 450200', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event1); }); it('should search string in sentences with parameter placeholders replaced and special characters removed in searched string', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'towards 450;200', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event1); }); it('should search string in sentences with parameter placeholders replaced and consecutive special characters removed in searched string', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'towards 450();200', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event1); }); it('should search string in sentences with multiple adjacent spaces reduced to one space', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'the angle (in ', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event2); }); it('should search string in sentences with leading and trailing white spaces', function () { const searchResultEvents1 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, ' the angle (in ', false, true, true, false, true, false ); expect(searchResultEvents1.size()).toBe(1); expect(searchResultEvents1.at(0).getEvent()).toBe(event2); const searchResultEvents2 = gd.EventsRefactorer.searchInEvents( gd.JsPlatform.get(), eventList, 'Delete OtherCharacter ', false, true, true, false, true, false ); expect(searchResultEvents2.size()).toBe(1); expect(searchResultEvents2.at(0).getEvent()).toBe(event2); }); }); }); describe('gd.EventsList', function () { it('can have events', function () { let list = new gd.EventsList(); list.insertEvent(new gd.StandardEvent(), 0); let lastEvent = list.insertEvent(new gd.StandardEvent(), 1); list.insertEvent(new gd.StandardEvent(), 1); expect(list.getEventsCount()).toBe(3); expect(list.getEventAt(2).ptr).toBe(lastEvent.ptr); list.delete(); }); it('can create lots of new events', function () { let project = new gd.ProjectHelper.createNewGDJSProject(); let list = new gd.EventsList(); for (let i = 0; i < 500; ++i) { let evt = list.insertNewEvent( project, 'BuiltinCommonInstructions::Standard', 0 ); expect(evt.getType()).toBe('BuiltinCommonInstructions::Standard'); expect(gd.asStandardEvent(list.getEventAt(0)).getType()).toBe( 'BuiltinCommonInstructions::Standard' ); expect(list.getEventAt(0).getType()).toBe( 'BuiltinCommonInstructions::Standard' ); } project.delete(); list.delete(); }); it('can tell if it contains an event', function () { let list = new gd.EventsList(); let parentEvent = list.insertEvent(new gd.StandardEvent(), 0); let subEvent = parentEvent .getSubEvents() .insertEvent(new gd.StandardEvent(), 0); expect(list.contains(parentEvent, false)).toBe(true); expect(list.contains(subEvent, false)).toBe(false); expect(list.contains(subEvent, true)).toBe(true); list.delete(); }); it('can move an event to another list without invalidating it/copying it in memory', function () { let list1 = new gd.EventsList(); let list2 = new gd.EventsList(); let list1ParentEvent = list1.insertEvent(new gd.StandardEvent(), 0); let list2ParentEvent = list2.insertEvent(new gd.StandardEvent(), 0); let originalSubEvent = list1ParentEvent .getSubEvents() .insertEvent(new gd.StandardEvent(), 0); let originalSubEventPtr = originalSubEvent.ptr; expect( list1ParentEvent .getSubEvents() .moveEventToAnotherEventsList( originalSubEvent, list2ParentEvent.getSubEvents(), 0 ) ).toBe(true); expect(list2ParentEvent.getSubEvents().getEventsCount()).toBe(1); let movedSubEvent = list2ParentEvent.getSubEvents().getEventAt(0); expect(movedSubEvent.ptr).toBe(originalSubEventPtr); list1.delete(); list2.delete(); }); }); describe('gd.BaseEvent', function () { it('can have a type', function () { let event = new gd.BaseEvent(); event.setType('Type1'); let event2 = new gd.BaseEvent(); event2.setType('Type2'); expect(event.getType()).toBe('Type1'); expect(event2.getType()).toBe('Type2'); event.delete(); event2.delete(); }); it('can be cloned', function () { let event = new gd.BaseEvent(); event.setType('Type1'); let event2 = event.clone(); expect(event.getType()).toBe('Type1'); expect(event2.getType()).toBe('Type1'); event.delete(); event2.delete(); }); it('can be de/serialized', function () { let event = new gd.BaseEvent(); expect(typeof event.serializeTo).toBe('function'); expect(typeof event.unserializeFrom).toBe('function'); event.delete(); }); }); describe('gd.ArbitraryEventsWorker', function () { describe('gd.EventsParametersLister', function () { it('can list parameters and their types', function () { let project = new gd.ProjectHelper.createNewGDJSProject(); let list = new gd.EventsList(); let evt = new gd.StandardEvent(); let actions = evt.getActions(); let act = new gd.Instruction(); act.setType('Delete'); act.setParametersCount(1); act.setParameter(0, 'MyObject'); actions.push_back(act); evt = list.insertEvent(evt, 0); let subEvt = new gd.StandardEvent(); let conditions = subEvt.getConditions(); let cnd = new gd.Instruction(); cnd.setType('PosX'); cnd.setParametersCount(3); cnd.setParameter(0, 'MyObject'); cnd.setParameter(1, '<'); cnd.setParameter(2, '300'); conditions.push_back(cnd); evt.getSubEvents().insertEvent(subEvt, 0); let parametersLister = new gd.EventsParametersLister(project); parametersLister.launch(list); expect(parametersLister.getParametersAndTypes().keys().size()).toBe(3); expect(parametersLister.getParametersAndTypes().get('MyObject')).toBe( 'object' ); // There are a lot of parameter definitions with 'expression' instead // of 'number'. They both means the same thing but 'expression' is // deprecated. expect(parametersLister.getParametersAndTypes().get('300')).toBe( 'number' ); project.delete(); list.delete(); }); }); describe('gd.EventsPositionFinder', function () { it('can find positions of a list of given events', function () { // evt0 // ├── evt00 <------- 0 // │   ├── evt000 // │   ├── evt001 // │   └── evt002 <-- 1 // ├── evt01 // │   └── evt010 <-- 2 // └── evt02 //    └── evt020 // evt1 <------------ 3 // └── evt10 <------- 4 const events = new gd.EventsList(); let evt0 = new gd.StandardEvent(); evt0 = events.insertEvent(evt0, 0); let evt1 = new gd.StandardEvent(); evt1 = events.insertEvent(evt1, 1); let evt00 = new gd.StandardEvent(); evt00 = evt0.getSubEvents().insertEvent(evt00, 0); let evt000 = new gd.StandardEvent(); let evt001 = new gd.StandardEvent(); let evt002 = new gd.StandardEvent(); evt000 = evt00.getSubEvents().insertEvent(evt000, 0); evt001 = evt00.getSubEvents().insertEvent(evt001, 1); evt002 = evt00.getSubEvents().insertEvent(evt002, 2); let evt01 = new gd.StandardEvent(); evt01 = evt0.getSubEvents().insertEvent(evt01, 1); let evt010 = new gd.StandardEvent(); evt010 = evt01.getSubEvents().insertEvent(evt010, 0); let evt02 = new gd.StandardEvent(); evt02 = evt0.getSubEvents().insertEvent(evt02, 2); let evt020 = new gd.StandardEvent(); evt020 = evt02.getSubEvents().insertEvent(evt020, 0); let evt10 = new gd.StandardEvent(); evt10 = evt1.getSubEvents().insertEvent(evt10, 0); const missingEvent = new gd.StandardEvent(); const positionFinder = new gd.EventsPositionFinder(); positionFinder.addEventToSearch(evt00); positionFinder.addEventToSearch(evt10); positionFinder.addEventToSearch(evt1); positionFinder.addEventToSearch(evt002); positionFinder.addEventToSearch(evt010); positionFinder.addEventToSearch(missingEvent); positionFinder.launch(events); expect(positionFinder.getPositions().size()).toBe(6); expect(positionFinder.getPositions().toJSArray()).toEqual([ 1, 10, 9, 4, 6, -1, ]); events.delete(); }); }); }); describe('gd.GroupEvent', function () { it('handle basic properties', function () { const evt = new gd.GroupEvent(); evt.setName('MyName'); evt.setSource('http://source.url'); evt.setCreationTimestamp(150); expect(evt.getName()).toBe('MyName'); expect(evt.getSource()).toBe('http://source.url'); expect(evt.getCreationTimestamp()).toBe(150); }); it('can be folded', function () { const evt = new gd.GroupEvent(); expect(evt.isFolded()).toBe(false); evt.setFolded(true); expect(evt.isFolded()).toBe(true); }); it('can remember parameters used to create the group from a template event', function () { const evt = new gd.GroupEvent(); let parameters = evt.getCreationParameters(); parameters.push_back('Param1'); parameters.push_back('Param2'); expect(evt.getCreationParameters().size()).toBe(2); expect(evt.getCreationParameters().get(0)).toBe('Param1'); expect(evt.getCreationParameters().get(1)).toBe('Param2'); parameters.clear(); expect(evt.getCreationParameters().size()).toBe(0); parameters.push_back('Param1'); expect(evt.getCreationParameters().get(0)).toBe('Param1'); }); }); describe('gd.StandardEvent', function () { it('initial values', function () { const evt = new gd.StandardEvent(); expect(evt.canHaveSubEvents()).toBe(true); expect(evt.isExecutable()).toBe(true); evt.delete(); }); it('conditions and actions', function () { const evt = new gd.StandardEvent(); let conditions = evt.getConditions(); expect(evt.getConditions().size()).toBe(0); let cnd = new gd.Instruction(); conditions.push_back(cnd); expect(evt.getConditions().size()).toBe(1); let actions = evt.getActions(); expect(evt.getActions().size()).toBe(0); let act = new gd.Instruction(); actions.push_back(act); expect(evt.getActions().size()).toBe(1); evt.delete(); }); }); describe('gd.CommentEvent', function () { it('initial values', function () { const evt = new gd.CommentEvent(); expect(evt.canHaveSubEvents()).toBe(false); expect(evt.isExecutable()).toBe(false); }); it('can have a comment', function () { const evt = new gd.CommentEvent(); evt.setComment('My nice comment about my events!'); expect(evt.getComment()).toBe('My nice comment about my events!'); }); it('can have a comment with UTF8 characters', function () { const evt = new gd.CommentEvent(); evt.setComment('Hello 官话 world!'); expect(evt.getComment()).toBe('Hello 官话 world!'); }); it('can have a background color', function () { const evt = new gd.CommentEvent(); evt.setBackgroundColor(100, 200, 255); expect(evt.getBackgroundColorRed()).toBe(100); expect(evt.getBackgroundColorGreen()).toBe(200); expect(evt.getBackgroundColorBlue()).toBe(255); }); it('can have a text color', function () { const evt = new gd.CommentEvent(); evt.setTextColor(101, 201, 254); expect(evt.getTextColorRed()).toBe(101); expect(evt.getTextColorGreen()).toBe(201); expect(evt.getTextColorBlue()).toBe(254); }); }); describe('gd.SpriteObject', function () { it('is a gd.Object', function () { const project = new gd.ProjectHelper.createNewGDJSProject(); let object = project .getObjects() .insertNewObject(project, 'Sprite', 'MySpriteObject'); expect(object instanceof gd.Object).toBe(true); expect(object.getVariables()).toBeTruthy(); project.delete(); }); it('can have animations', function () { const obj = new gd.SpriteObject(); const animations = obj.getAnimations(); animations.addAnimation(new gd.Animation()); animations.addAnimation(new gd.Animation()); expect(animations.getAnimationsCount()).toBe(2); animations.removeAnimation(1); expect(animations.getAnimationsCount()).toBe(1); }); it('can swap animations', function () { const obj = new gd.SpriteObject(); const animations = obj.getAnimations(); animations.removeAllAnimations(); let anim1 = new gd.Animation(); let anim2 = new gd.Animation(); let sprite1 = new gd.Sprite(); let sprite2 = new gd.Sprite(); sprite1.setImageName('image1'); sprite2.setImageName('image2'); anim1.setDirectionsCount(1); anim2.setDirectionsCount(1); anim1.getDirection(0).addSprite(sprite1); anim2.getDirection(0).addSprite(sprite2); animations.addAnimation(anim1); animations.addAnimation(anim2); expect( animations.getAnimation(0).getDirection(0).getSprite(0).getImageName() ).toBe('image1'); animations.swapAnimations(0, 1); expect( animations.getAnimation(0).getDirection(0).getSprite(0).getImageName() ).toBe('image2'); }); describe('gd.Direction', function () { it('can swap sprites', function () { const direction = new gd.Direction(); const sprite1 = new gd.Sprite(); const sprite2 = new gd.Sprite(); sprite1.setImageName('image1'); sprite2.setImageName('image2'); direction.addSprite(sprite1); direction.addSprite(sprite2); expect(direction.getSprite(0).getImageName()).toBe('image1'); direction.swapSprites(0, 1); expect(direction.getSprite(0).getImageName()).toBe('image2'); direction.swapSprites(1, 0); expect(direction.getSprite(0).getImageName()).toBe('image1'); direction.delete(); }); it('can move sprites', function () { const direction = new gd.Direction(); const sprite1 = new gd.Sprite(); const sprite2 = new gd.Sprite(); const sprite3 = new gd.Sprite(); sprite1.setImageName('image1'); sprite2.setImageName('image2'); sprite3.setImageName('image3'); direction.addSprite(sprite1); direction.addSprite(sprite2); direction.addSprite(sprite3); expect(direction.getSprite(0).getImageName()).toBe('image1'); direction.moveSprite(0, 2); expect(direction.getSprite(0).getImageName()).toBe('image2'); expect(direction.getSprite(1).getImageName()).toBe('image3'); expect(direction.getSprite(2).getImageName()).toBe('image1'); direction.swapSprites(1, 1); expect(direction.getSprite(0).getImageName()).toBe('image2'); expect(direction.getSprite(1).getImageName()).toBe('image3'); expect(direction.getSprite(2).getImageName()).toBe('image1'); direction.swapSprites(1, 0); expect(direction.getSprite(0).getImageName()).toBe('image3'); expect(direction.getSprite(1).getImageName()).toBe('image2'); expect(direction.getSprite(2).getImageName()).toBe('image1'); direction.swapSprites(999, 998); expect(direction.getSprite(0).getImageName()).toBe('image3'); expect(direction.getSprite(1).getImageName()).toBe('image2'); expect(direction.getSprite(2).getImageName()).toBe('image1'); direction.delete(); }); it('can have metadata', function () { const direction = new gd.Direction(); expect(direction.getMetadata()).toBe(''); direction.setMetadata('{test: 1}'); expect(direction.getMetadata()).toBe('{test: 1}'); direction.delete(); }); }); describe('gd.Sprite', function () { it('can have default points', function () { let sprite1 = new gd.Sprite(); sprite1.getCenter().setX(2); sprite1.getCenter().setY(3); sprite1.getOrigin().setX(4); sprite1.getOrigin().setY(5); expect(sprite1.getCenter().getX()).toBe(2); expect(sprite1.getCenter().getY()).toBe(3); expect(sprite1.getOrigin().getX()).toBe(4); expect(sprite1.getOrigin().getY()).toBe(5); }); it('can have custom points', function () { let sprite1 = new gd.Sprite(); let point = new gd.Point('test'); sprite1.addPoint(point); point.delete(); expect(sprite1.hasPoint('test')).toBe(true); sprite1.getPoint('test').setX(1); sprite1.getPoint('test').setY(2); expect(sprite1.getPoint('test').getX()).toBe(1); expect(sprite1.getPoint('test').getY()).toBe(2); }); }); }); describe('gd.MetadataProvider', function () { it('can return metadata about expressions (even if they do not exist)', function () { expect( gd.MetadataProvider.getExpressionMetadata( gd.JsPlatform.get(), 'NotExistingExpression' ).getFullName() ).toBe(''); }); describe('gd.ObjectMetadata', function () { it('can return standard information about Sprite object', function () { let objMetadata = gd.MetadataProvider.getObjectMetadata( gd.JsPlatform.get(), 'Sprite' ); expect(objMetadata.getName()).toBe('Sprite'); expect(objMetadata.getFullName()).toBe('Sprite'); expect(objMetadata.getDescription().length).not.toBe(0); expect(objMetadata.getIconFilename().length).not.toBe(0); }); it('can have conditions and actions added at the same time for booleans', function () { const extension = new gd.PlatformExtension(); extension.setExtensionInformation( 'TestExtensionName', 'Full name of test extension', 'Description of test extension', 'Author of test extension', 'License of test extension' ); const fakeObject = new gd.ObjectJsImplementation(); const objectMetadata = extension.addObject( 'FakeObject', 'FakeObject', 'This is FakeObject', '', fakeObject ); objectMetadata .addExpressionAndConditionAndAction( 'boolean', 'Disabled', 'Disabled', 'the object is disabled', 'disabled', '', 'Disable.png' ) .addParameter('object', 'My object', 'MyObject', false) .useStandardParameters( 'boolean', gd.ParameterOptions.makeNewOptions() ) .setFunctionName('setDisabled') .setGetter('isDisabled'); expect( objectMetadata .getAllConditions() .has('TestExtensionName::FakeObject::Disabled') ).toBe(true); expect( objectMetadata .getAllActions() .has('TestExtensionName::FakeObject::SetDisabled') ).toBe(true); // Check sentences are properly set up. expect( objectMetadata .getAllConditions() .get('TestExtensionName::FakeObject::Disabled') .getSentence() ).toBe('_PARAM0_ is disabled'); expect( objectMetadata .getAllActions() .get('TestExtensionName::FakeObject::SetDisabled') .getSentence() ).toBe('Set _PARAM0_ as disabled: _PARAM1_'); }); }); describe('gd.BehaviorMetadata', function () { it('have standard methods to get information', function () { let autoMetadata = gd.MetadataProvider.getBehaviorMetadata( gd.JsPlatform.get(), 'NotExistingBehavior' ); expect(autoMetadata.getFullName).not.toBe(undefined); expect(autoMetadata.getDefaultName).not.toBe(undefined); expect(autoMetadata.getDescription).not.toBe(undefined); expect(autoMetadata.getGroup).not.toBe(undefined); expect(autoMetadata.getIconFilename).not.toBe(undefined); expect(autoMetadata.getObjectType).not.toBe(undefined); }); it('can have conditions and actions added at the same time for booleans', function () { const extension = new gd.PlatformExtension(); extension.setExtensionInformation( 'TestExtensionName', 'Full name of test extension', 'Description of test extension', 'Author of test extension', 'License of test extension' ); const dummyBehavior = new gd.BehaviorJsImplementation(); dummyBehavior.initializeContent = function (behaviorContent) {}; const behaviorMetadata = extension.addBehavior( 'DummyBehavior', 'Dummy behavior for testing', 'DummyBehavior', 'Do nothing.', '', '', 'DummyBehavior', dummyBehavior, new gd.BehaviorsSharedData() ); behaviorMetadata .addExpressionAndConditionAndAction( 'boolean', 'Disabled', 'Disabled', 'the object is disabled', 'disabled', '', 'Disable.png' ) .addParameter('object', 'My object', '', false) .useStandardParameters( 'boolean', gd.ParameterOptions.makeNewOptions() ) .setFunctionName('setDisabled') .setGetter('isDisabled'); expect( behaviorMetadata .getAllConditions() .has('TestExtensionName::DummyBehavior::Disabled') ).toBe(true); expect( behaviorMetadata .getAllActions() .has('TestExtensionName::DummyBehavior::SetDisabled') ).toBe(true); // Check sentences are properly set up. expect( behaviorMetadata .getAllConditions() .get('TestExtensionName::DummyBehavior::Disabled') .getSentence() ).toBe('_PARAM0_ is disabled'); expect( behaviorMetadata .getAllActions() .get('TestExtensionName::DummyBehavior::SetDisabled') .getSentence() ).toBe('Set _PARAM0_ as disabled: _PARAM1_'); }); }); describe('gd.EffectMetadata', function () { it('have standard methods to get information', function () { let autoMetadata = gd.MetadataProvider.getEffectMetadata( gd.JsPlatform.get(), 'NotExistingEffect' ); expect(autoMetadata.getType).not.toBe(undefined); expect(autoMetadata.getFullName).not.toBe(undefined); expect(autoMetadata.getDescription).not.toBe(undefined); expect(autoMetadata.getProperties).not.toBe(undefined); }); }); }); describe('gd.ResourcesMergingHelper (and gd.AbstractFileSystemJS)', function () { it('should export files of the project', function () { // Create a project with a mix of resources const project = new gd.ProjectHelper.createNewGDJSProject(); const layout = project.insertNewLayout('Scene', 0); const resource = new gd.ImageResource(); const resource2 = new gd.ImageResource(); const resource3 = new gd.ImageResource(); const resource4 = new gd.ImageResource(); const resource5 = new gd.ImageResource(); resource.setName('MyResource'); resource.setFile('MyResource.png'); resource2.setName('MyAudioResource'); resource2.setFile('MyResource.wav'); resource3.setName('test/MyResource.png'); resource3.setFile('test/MyResource.png'); resource4.setName('test/MyResourceWithExtension'); resource4.setFile('test/MyResourceWithExtension'); resource5.setName('test/sub/folder/MyResourceWithExtension'); resource5.setFile('test/sub/folder/MyResourceWithExtension'); project.getResourcesManager().addResource(resource); project.getResourcesManager().addResource(resource2); project.getResourcesManager().addResource(resource3); project.getResourcesManager().addResource(resource4); project.getResourcesManager().addResource(resource5); const fs = makeFakeAbstractFileSystem(gd, {}); // Check that ResourcesMergingHelper can update the filenames const resourcesMergingHelper = new gd.ResourcesMergingHelper( project.getResourcesManager(), fs ); resourcesMergingHelper.setBaseDirectory('/my/project/'); gd.ResourceExposer.exposeWholeProjectResources( project, resourcesMergingHelper ); const oldAndNewFilenames = resourcesMergingHelper.getAllResourcesOldAndNewFilename(); expect(oldAndNewFilenames.get('/my/project/MyResource.png')).toBe( 'MyResource.png' ); expect(oldAndNewFilenames.get('/my/project/MyResource.wav')).toBe( 'MyResource.wav' ); expect(oldAndNewFilenames.get('/my/project/test/MyResource.png')).toBe( 'MyResource2.png' ); expect( oldAndNewFilenames.get('/my/project/test/MyResourceWithExtension') ).toBe('MyResourceWithExtension'); expect( oldAndNewFilenames.get( '/my/project/test/sub/folder/MyResourceWithExtension' ) ).toBe('MyResourceWithExtension2'); resourcesMergingHelper.delete(); project.delete(); }); }); describe('gd.ProjectResourcesCopier (and gd.AbstractFileSystemJS)', function () { it('should export files of the project', function () { // Create a project with a mix of resources, stored in /my/project folder. const project = new gd.ProjectHelper.createNewGDJSProject(); project.setProjectFile('/my/project/project.json'); const layout = project.insertNewLayout('Scene', 0); const resource = new gd.ImageResource(); const resource2 = new gd.ImageResource(); const resource3 = new gd.ImageResource(); const resource4 = new gd.ImageResource(); const resource5 = new gd.ImageResource(); resource.setName('MyResource'); resource.setFile('MyResource.png'); resource2.setName('MyAudioResource'); resource2.setFile('MyResource.wav'); resource3.setName('MyAbsoluteResource'); resource3.setFile('/my/absolute/path/MyResource2.png'); resource4.setName('test/MyResourceWithoutExtension'); resource4.setFile('test/MyResourceWithoutExtension'); resource5.setName('test/sub/folder/MyResourceWithoutExtension'); resource5.setFile('test/sub/folder/MyResourceWithoutExtension'); // Same filename as resource4 project.getResourcesManager().addResource(resource); project.getResourcesManager().addResource(resource2); project.getResourcesManager().addResource(resource3); project.getResourcesManager().addResource(resource4); project.getResourcesManager().addResource(resource5); const fs = makeFakeAbstractFileSystem(gd, {}); // Check that resources can be copied to another folder: // * including absolute files. // * preserving relative file structures fs.copyFile.mockClear(); gd.ProjectResourcesCopier.copyAllResourcesTo( project, fs, '/my/new/folder', false, false, true ); expect(fs.copyFile).toHaveBeenCalledTimes(5); // All 5 resources are copied expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.png', '/my/new/folder/MyResource.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.wav', '/my/new/folder/MyResource.wav' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/absolute/path/MyResource2.png', '/my/new/folder/MyResource2.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/MyResourceWithoutExtension', '/my/new/folder/test/MyResourceWithoutExtension' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/sub/folder/MyResourceWithoutExtension', '/my/new/folder/test/sub/folder/MyResourceWithoutExtension' ); // Check that resources can be copied to another folder: // * including absolute files. // * NOT preserving relative file structures // Check that filename collisions are avoided. fs.copyFile.mockClear(); gd.ProjectResourcesCopier.copyAllResourcesTo( project, fs, '/my/new/folder', false, false, false ); expect(fs.copyFile).toHaveBeenCalledTimes(5); // All 5 resources are copied expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.png', '/my/new/folder/MyResource.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.wav', '/my/new/folder/MyResource.wav' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/absolute/path/MyResource2.png', '/my/new/folder/MyResource2.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/MyResourceWithoutExtension', '/my/new/folder/MyResourceWithoutExtension' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/sub/folder/MyResourceWithoutExtension', '/my/new/folder/MyResourceWithoutExtension2' ); // Check that resources can be copied to another folder: // * without touching absolute files. // * preserving relative file structures fs.copyFile.mockClear(); gd.ProjectResourcesCopier.copyAllResourcesTo( project, fs, '/my/new/folder', false, true, true ); expect(fs.copyFile).toHaveBeenCalledTimes(4); // Only the 4 relative resources are copied expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.png', '/my/new/folder/MyResource.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.wav', '/my/new/folder/MyResource.wav' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/MyResourceWithoutExtension', '/my/new/folder/test/MyResourceWithoutExtension' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/sub/folder/MyResourceWithoutExtension', '/my/new/folder/test/sub/folder/MyResourceWithoutExtension' ); // Check that resources can be copied to another folder: // * without touching absolute files. // * NOT preserving relative file structures // Check that filename collisions are avoided. fs.copyFile.mockClear(); gd.ProjectResourcesCopier.copyAllResourcesTo( project, fs, '/my/new/folder', false, true, false ); expect(fs.copyFile).toHaveBeenCalledTimes(5); // All 5 resources are copied expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.png', '/my/new/folder/MyResource.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/MyResource.wav', '/my/new/folder/MyResource.wav' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/absolute/path/MyResource2.png', '/my/new/folder/MyResource2.png' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/MyResourceWithoutExtension', '/my/new/folder/MyResourceWithoutExtension' ); expect(fs.copyFile).toHaveBeenCalledWith( '/my/project/test/sub/folder/MyResourceWithoutExtension', '/my/new/folder/MyResourceWithoutExtension2' ); project.delete(); }); }); describe('gd.Exporter (and gd.AbstractFileSystemJS)', function () { it('should export a layout for preview', function (done) { let fs = new gd.AbstractFileSystemJS(); let project = new gd.ProjectHelper.createNewGDJSProject(); let layout = project.insertNewLayout('Scene', 0); fs.mkDir = fs.clearDir = function () {}; fs.getTempDir = function (path) { return '/tmp/'; }; fs.fileNameFrom = function (fullpath) { return path.basename(fullpath); }; fs.dirNameFrom = function (fullpath) { return path.dirname(fullpath); }; fs.readDir = function () { return new gd.VectorString(); }; fs.writeToFile = function (path, content) { //Validate that some code have been generated: expect(content).toMatch('runtimeScene.getOnceTriggers().startNewFrame'); done(); }; const exporter = new gd.Exporter(fs, 'fake-gdjs-root'); const previewExportOptions = new gd.PreviewExportOptions( project, '/path/for/export/' ); previewExportOptions.setLayoutName('Scene'); exporter.exportProjectForPixiPreview(previewExportOptions); previewExportOptions.delete(); exporter.delete(); project.delete(); fs.delete(); }); }); describe('gd.EventsRemover', function () { it('should remove events', function () { let list = new gd.EventsList(); let event1 = list.insertEvent(new gd.StandardEvent(), 0); let event2 = list.insertEvent(new gd.StandardEvent(), 1); let event3 = list.insertEvent(new gd.StandardEvent(), 2); let remover = new gd.EventsRemover(); remover.addEventToRemove(event1); remover.addEventToRemove(event3); remover.launch(list); expect(list.getEventsCount()).toBe(1); expect(list.getEventAt(0)).toBe(event2); }); }); describe('gd.WholeProjectRefactorer', function () { it('should rename and delete an object', function () { let project = new gd.ProjectHelper.createNewGDJSProject(); let layout = project.insertNewLayout('Scene', 0); let instance1 = layout.getInitialInstances().insertNewInitialInstance(); let instance2 = layout.getInitialInstances().insertNewInitialInstance(); instance1.setObjectName('Object1'); instance2.setObjectName('Object2'); gd.WholeProjectRefactorer.objectOrGroupRenamedInScene( project, layout, 'Object1', 'Object3', /* isObjectGroup=*/ false ); expect(layout.getInitialInstances().hasInstancesOfObject('Object1')).toBe( false ); expect(layout.getInitialInstances().hasInstancesOfObject('Object2')).toBe( true ); expect(layout.getInitialInstances().hasInstancesOfObject('Object3')).toBe( true ); gd.WholeProjectRefactorer.objectRemovedInScene( project, layout, 'Object3', /* isObjectGroup=*/ false, true ); expect(layout.getInitialInstances().hasInstancesOfObject('Object1')).toBe( false ); expect(layout.getInitialInstances().hasInstancesOfObject('Object2')).toBe( true ); expect(layout.getInitialInstances().hasInstancesOfObject('Object3')).toBe( false ); }); // See other tests in WholeProjectRefactorer.cpp }); describe('gd.ExpressionParser2 and gd.ExpressionValidator', function () { let project = null; let layout = null; beforeAll(() => { project = new gd.ProjectHelper.createNewGDJSProject(); layout = project.insertNewLayout('Scene', 0); layout .getObjects() .insertNewObject(project, 'Sprite', 'MySpriteObject', 0); }); function testExpression( type, expression, expectedError, expectedErrorPosition, expectedError2, expectedErrorPosition2 ) { const parser = new gd.ExpressionParser2(); const expressionNode = parser.parseExpression(expression).get(); const expressionValidator = new gd.ExpressionValidator( gd.JsPlatform.get(), gd.ProjectScopedContainers.makeNewProjectScopedContainersForProjectAndLayout( project, layout ), type, '' ); expressionNode.visit(expressionValidator); if (expectedError2) { expect(expressionValidator.getAllErrors().size()).toBe(2); expect(expressionValidator.getAllErrors().at(0).getMessage()).toBe( expectedError ); if (expectedErrorPosition) expect( expressionValidator.getAllErrors().at(0).getStartPosition() ).toBe(expectedErrorPosition); expect(expressionValidator.getAllErrors().at(1).getMessage()).toBe( expectedError2 ); if (expectedErrorPosition2) expect( expressionValidator.getAllErrors().at(1).getStartPosition() ).toBe(expectedErrorPosition2); } else if (expectedError) { expect(expressionValidator.getAllErrors().size()).toBe(1); expect(expressionValidator.getAllErrors().at(0).getMessage()).toBe( expectedError ); if (expectedErrorPosition) expect( expressionValidator.getAllErrors().at(0).getStartPosition() ).toBe(expectedErrorPosition); } else { expect(expressionValidator.getAllErrors().size()).toBe(0); } expressionValidator.delete(); parser.delete(); } it('can parse valid expressions (number)', function () { testExpression('number', '1+1'); testExpression('number', '2-3'); testExpression('number', '4/5'); testExpression('number', '6*7'); testExpression('number', '8 + 9'); testExpression('number', '10 + 11'); testExpression('number', '12 + 13 - 14'); testExpression('number', ' 15 + 16 - 17 '); testExpression('number', '.14'); testExpression('number', '3.'); }); it('can parse valid expressions (string)', function () { testExpression('string', '"Hello"'); testExpression('string', '"Hello" + " " + "World"'); }); it('can parse valid expressions ("number|string" type)', function () { testExpression('number|string', '1+1'); testExpression('number|string', '2-3'); testExpression('number|string', '4/5'); testExpression('number|string', '6*7'); testExpression('number|string', '"Hello"'); testExpression('number|string', '"Hello" + " " + "World"'); }); it('report errors in invalid expressions', function () { testExpression( 'number', '1//2', 'You must enter a number or a valid expression call.', 2 ); testExpression('number', 'bad expression', 'You must enter a number.', 0); testExpression( 'number', '1 + test()', "Cannot find an expression with this name: test\nDouble check that you've not made any typo in the name.", 4 ); testExpression( 'number', '3..14', 'More than one term was found. Verify that your expression is properly written.', 2, 'No operator found. Did you forget to enter an operator (like +, -, * or /) between numbers or expressions?', 2 ); testExpression( 'string', '="Mynewscene"', 'You must enter a text (between quotes) or a valid expression call.', 0 ); }); it('report errors in invalid expressions ("number|string" type)', function () { testExpression( 'number|string', '123 + "World"', 'You entered a text, but a number was expected.', 6 ); testExpression( 'number|string', '"World" + 123', 'You entered a number, but a text was expected (in quotes).', 10 ); testExpression( 'number|string', '"World" + ToNumber("123")', 'You tried to use an expression that returns a number, but a string is expected. Use `ToString` if you need to convert a number to a string.', 10 ); testExpression( 'number|string', '123 + ToString(456)', 'You tried to use an expression that returns a string, but a number is expected. Use `ToNumber` if you need to convert a string to a number.', 6 ); }); it('can parse valid expressions with free functions', function () { testExpression('number', '1+sin(3.14)'); testExpression('number', 'abs(-5)'); testExpression('number', 'abs(-5) + cos(sin(3))'); testExpression('number', 'atan2(-5, 3)'); testExpression('number', 'MouseX("", 0) + 1'); }); it('can report errors when using too much arguments', function () { testExpression( 'number', 'abs(-5, 3)', "This parameter was not expected by this expression. Remove it or verify that you've entered the proper expression name. The number of parameters must be exactly 1" ); testExpression( 'number', 'MouseX("", 0, 0) + 1', "This parameter was not expected by this expression. Remove it or verify that you've entered the proper expression name. The number of parameters must be: 0-2" ); }); it('can parse valid expressions with free functions having optional parameters', function () { testExpression('number', 'MouseX() + 1'); testExpression('number', 'MouseX("") + 1'); }); it('can parse expressions with objects functions', function () { testExpression('number', 'MySpriteObject.X()'); testExpression('number', 'MySpriteObject.X() + 1'); testExpression('number', 'MySpriteObject.PointX("Point")'); }); it('can report errors when using too much arguments in object functions', function () { testExpression( 'number', 'MySpriteObject.PointX("Point", 2)', "This parameter was not expected by this expression. Remove it or verify that you've entered the proper expression name. The number of parameters must be exactly 1" ); }); it('can parse arguments being expressions', function () { testExpression('number', 'MouseX(VariableString(myVariable), 0) + 1'); }); }); describe('gd.Vector2f', function () { describe('gd.VectorVector2f', function () { it('can be used to manipulate a vector of gd.Vector2f', function () { const vectorVector2f = new gd.VectorVector2f(); const vector2f = new gd.Vector2f(); vectorVector2f.push_back(vector2f); vectorVector2f.push_back(vector2f); vectorVector2f.push_back(vector2f); expect(vectorVector2f.size()).toBe(3); vectorVector2f.at(0).set_x(1); vectorVector2f.at(1).set_x(2); vectorVector2f.at(2).set_x(3); expect(vectorVector2f.at(0).get_x()).toBe(1); expect(vectorVector2f.at(1).get_x()).toBe(2); expect(vectorVector2f.at(2).get_x()).toBe(3); gd.moveVector2fInVector(vectorVector2f, 2, 0); expect(vectorVector2f.at(0).get_x()).toBe(3); expect(vectorVector2f.at(1).get_x()).toBe(1); expect(vectorVector2f.at(2).get_x()).toBe(2); gd.removeFromVectorVector2f(vectorVector2f, 1); expect(vectorVector2f.size()).toBe(2); expect(vectorVector2f.at(0).get_x()).toBe(3); expect(vectorVector2f.at(1).get_x()).toBe(2); vectorVector2f.clear(); expect(vectorVector2f.size()).toBe(0); }); }); }); describe('gd.PlatformExtension', function () { const makeTestExtension = () => { const extension = new gd.PlatformExtension(); extension .setExtensionInformation( 'TestExtensionName', 'Full name of test extension', 'Description of test extension', 'Author of test extension', 'License of test extension' ) .setExtensionHelpPath('/path/to/extension/help'); return extension; }; it('can be created and have basic information filled', function () { const extension = makeTestExtension(); expect(extension.getName()).toBe('TestExtensionName'); expect(extension.getFullName()).toBe('Full name of test extension'); expect(extension.getDescription()).toBe('Description of test extension'); expect(extension.getAuthor()).toBe('Author of test extension'); expect(extension.getLicense()).toBe('License of test extension'); expect(extension.getHelpPath()).toBe('/path/to/extension/help'); extension.delete(); }); it('can have actions and conditions added', function () { const extension = makeTestExtension(); extension .addCondition( 'BannerShowing', 'Banner showing', 'Check if there is a banner being displayed.', 'Banner is showing', 'AdMob', 'JsPlatform/Extensions/admobicon24.png', 'JsPlatform/Extensions/admobicon16.png' ) .getCodeExtraInformation() .setIncludeFile('Extensions/AdMob/admobtools.js') .setFunctionName('gdjs.adMob.isBannerShowing'); expect( extension.getAllConditions().has('TestExtensionName::BannerShowing') ).toBe(true); const condition = extension .getAllConditions() .get('TestExtensionName::BannerShowing'); expect(condition.getFullName()).toBe('Banner showing'); expect(condition.isHidden()).toBe(false); // Check also the API to duplicate a condition. extension .addDuplicatedCondition('AnotherCondition', 'BannerShowing') .setHidden(); expect( extension.getAllConditions().has('TestExtensionName::AnotherCondition') ).toBe(true); const copiedCondition = extension .getAllConditions() .get('TestExtensionName::AnotherCondition'); expect(copiedCondition.getFullName()).toBe('Banner showing'); expect(copiedCondition.isHidden()).toBe(true); extension.delete(); }); it('can have expressions and conditions added at the same time', function () { const extension = makeTestExtension(); extension .addExpressionAndCondition( 'number', 'PlayerHealth', 'Health of the player', 'The health of the player, from 0 to 100.', 'the health of the player', 'Health Management', 'SomeHealthIcon.png' ) .addCodeOnlyParameter('currentScene', '') .addParameter('string', 'Some stuff', '', false) .setParameterLongDescription('Blabla') .setFunctionName('some.method.to.getPlayerHealth') .useStandardParameters('number', gd.ParameterOptions.makeNewOptions()); expect( extension.getAllConditions().has('TestExtensionName::PlayerHealth') ).toBe(true); const declaredCondition = extension .getAllConditions() .get('TestExtensionName::PlayerHealth'); expect(declaredCondition.getParametersCount()).toBe(4); expect( extension.getAllExpressions().has('TestExtensionName::PlayerHealth') ).toBe(true); const declaredExpression = extension .getAllExpressions() .get('TestExtensionName::PlayerHealth'); expect(declaredExpression.getParametersCount()).toBe(2); }); it('can have expressions, conditions and actions added at the same time', function () { const extension = makeTestExtension(); extension .addExpressionAndConditionAndAction( 'number', 'PlayerHealth', 'Health of the player', 'The health of the player, from 0 to 100.', 'the health of the player', 'Health Management', 'SomeHealthIcon.png' ) .addCodeOnlyParameter('currentScene', '') .addParameter('string', 'Some stuff', '', false) .setParameterLongDescription('Blabla') .setFunctionName('some.method.to.getPlayerHealth') .useStandardParameters('number', gd.ParameterOptions.makeNewOptions()); expect( extension.getAllConditions().has('TestExtensionName::PlayerHealth') ).toBe(true); const declaredCondition = extension .getAllConditions() .get('TestExtensionName::PlayerHealth'); expect(declaredCondition.getParametersCount()).toBe(4); expect( extension.getAllActions().has('TestExtensionName::SetPlayerHealth') ).toBe(true); const declaredAction = extension .getAllActions() .get('TestExtensionName::SetPlayerHealth'); expect(declaredAction.getParametersCount()).toBe(4); expect( extension.getAllExpressions().has('TestExtensionName::PlayerHealth') ).toBe(true); const declaredExpression = extension .getAllExpressions() .get('TestExtensionName::PlayerHealth'); expect(declaredExpression.getParametersCount()).toBe(2); }); it('can have conditions and actions added at the same time for booleans', function () { const extension = makeTestExtension(); extension .addExpressionAndConditionAndAction( 'boolean', 'Disabled', 'Disabled', 'the object is disabled', 'disabled', '', 'Disable.png' ) .addParameter('object', 'My object', 'MyObject', false) .useStandardParameters('boolean', gd.ParameterOptions.makeNewOptions()) .setFunctionName('setDisabled') .setGetter('isDisabled'); expect( extension.getAllConditions().has('TestExtensionName::Disabled') ).toBe(true); const declaredCondition = extension .getAllConditions() .get('TestExtensionName::Disabled'); expect( extension.getAllActions().has('TestExtensionName::SetDisabled') ).toBe(true); const declaredAction = extension .getAllActions() .get('TestExtensionName::SetDisabled'); expect(declaredAction.getParametersCount()).toBe(2); }); }); describe('gd.Platform (using gd.JsPlatform)', function () { it('can have extension added and removed', function () { const extension = new gd.PlatformExtension(); extension.setExtensionInformation( 'MyNewExtension', 'Full name of test extension', 'Description of test extension', 'Author of test extension', 'License of test extension' ); gd.JsPlatform.get().addNewExtension(extension); expect(gd.JsPlatform.get().isExtensionLoaded('MyNewExtension')).toBe( true ); gd.JsPlatform.get().removeExtension('MyNewExtension'); expect(gd.JsPlatform.get().isExtensionLoaded('MyNewExtension')).toBe( false ); gd.JsPlatform.get().addNewExtension(extension); gd.JsPlatform.get().addNewExtension(extension); expect(gd.JsPlatform.get().isExtensionLoaded('MyNewExtension')).toBe( true ); gd.JsPlatform.get().removeExtension('MyNewExtension'); expect(gd.JsPlatform.get().isExtensionLoaded('MyNewExtension')).toBe( false ); extension.delete(); }); it('has a namespace separator', function () { expect(gd.PlatformExtension.getNamespaceSeparator()).toBe('::'); }); }); describe('gd.ParameterMetadata', function () { it('can tell the type of a parameter', function () { expect(gd.ParameterMetadata.isObject('object')).toBe(true); expect(gd.ParameterMetadata.isObject('objectPtr')).toBe(true); expect(gd.ParameterMetadata.isObject('123')).toBe(false); expect(gd.ParameterMetadata.isBehavior('behavior')).toBe(true); expect(gd.ParameterMetadata.isBehavior('behavior34234')).toBe(false); }); it('can have attributes and be serialized', function () { const parameter1 = new gd.ParameterMetadata(); parameter1.setType('objectList'); parameter1.setName('MyObjectWithoutType'); parameter1.setDescription('The first object to be used'); parameter1.setLongDescription('A long description'); parameter1.setDefaultValue('Default value'); parameter1.setOptional(true); const serializerElement = new gd.SerializerElement(); parameter1.serializeTo(serializerElement); const parameter2 = new gd.ParameterMetadata(); parameter2.unserializeFrom(serializerElement); expect(parameter1.getType()).toBe('objectList'); expect(parameter1.getName()).toBe('MyObjectWithoutType'); expect(parameter1.getDescription()).toBe('The first object to be used'); expect(parameter1.getLongDescription()).toBe('A long description'); expect(parameter1.getDefaultValue()).toBe('Default value'); expect(parameter1.isOptional()).toBe(true); }); }); describe('gd.ParameterMetadataTools', function () { it('can create an object container from parameters', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const eventsFunction = new gd.EventsFunction(); const parameters = eventsFunction.getParameters(); parameters .addNewParameter('MyObjectWithoutType') .setType('objectList') .setDescription('The first object to be used'); parameters .addNewParameter('MyNumber') .setType('expression') .setDescription('Some number'); // No name for this parameter parameters .addNewParameter('') .setType('objectList') .setExtraInfo('Sprite') .setDescription('This parameter will be skipped, as it has no name'); parameters .addNewParameter('MyString') .setType('string') .setDescription('Some string'); parameters .addNewParameter('MySpriteObject') .setType('objectList') .setExtraInfo('Sprite') .setDescription('The second object to be used, a sprite'); parameters .addNewParameter('MyFirstBehavior') .setType('behavior') .setExtraInfo('AnchorBehavior::AnchorBehavior') .setDescription('The first behavior of the first sprite'); parameters .addNewParameter('MySecondBehavior') .setType('behavior') .setExtraInfo('PlatformBehavior::PlatformBehavior') .setDescription('The second behavior of the first sprite'); parameters .addNewParameter('MySpriteObject2') .setType('objectList') .setExtraInfo('Sprite') .setDescription('The second object to be used, a sprite'); expect(parameters.getParametersCount()).toBe(8); parameters.removeParameter('MySpriteObject2'); expect(parameters.getParametersCount()).toBe(7); objectsContainer = new gd.ObjectsContainer(gd.ObjectsContainer.Function); gd.ParameterMetadataTools.parametersToObjectsContainer( project, parameters, objectsContainer ); // Check that object parameters are considered as objects in the objects container. expect(objectsContainer.getObjectsCount()).toBe(2); expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true); const objectWithoutType = objectsContainer.getObject( 'MyObjectWithoutType' ); expect(objectWithoutType.getType()).toBe(''); const mySpriteObject = objectsContainer.getObject('MySpriteObject'); expect(objectsContainer.getObject('MySpriteObject').getType()).toBe( 'Sprite' ); // Check that behaviors were also added AND that default behaviors are also present. expect( objectsContainer .getObject('MySpriteObject') .getAllBehaviorNames() .toJSArray() ).toEqual([ 'Animation', 'Effect', 'Flippable', 'MyFirstBehavior', 'MySecondBehavior', 'Opacity', 'Resizable', 'Scale', ]); expect( objectsContainer .getObject('MyObjectWithoutType') .getAllBehaviorNames() .toJSArray() ).toEqual([]); // Call a second time and verify no changes. gd.ParameterMetadataTools.parametersToObjectsContainer( project, parameters, objectsContainer ); expect(objectsContainer.getObjectsCount()).toBe(2); expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true); expect( objectsContainer .getObject('MySpriteObject') .getAllBehaviorNames() .toJSArray() ).toEqual([ 'Animation', 'Effect', 'Flippable', 'MyFirstBehavior', 'MySecondBehavior', 'Opacity', 'Resizable', 'Scale', ]); expect( objectsContainer .getObject('MyObjectWithoutType') .getAllBehaviorNames() .toJSArray() ).toEqual([]); // Verify that objects are even stable in memory. expect(objectWithoutType).toBe( objectsContainer.getObject('MyObjectWithoutType') ); expect(gd.getPointer(objectWithoutType)).toBe( gd.getPointer(objectsContainer.getObject('MyObjectWithoutType')) ); expect(mySpriteObject).toBe(objectsContainer.getObject('MySpriteObject')); expect(gd.getPointer(mySpriteObject)).toBe( gd.getPointer(objectsContainer.getObject('MySpriteObject')) ); // Change an object type, rename a behavior and add a new object. parameters.getParameter('MyObjectWithoutType').setExtraInfo('Sprite'); parameters .getParameter('MySecondBehavior') .setName('MyRenamedSecondBehavior'); parameters .addNewParameter('MySpriteObject3') .setType('objectList') .setExtraInfo('Sprite') .setDescription('The third object to be used, a sprite'); // Verify changes are propagated. gd.ParameterMetadataTools.parametersToObjectsContainer( project, parameters, objectsContainer ); expect(objectsContainer.getObjectsCount()).toBe(3); expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject3')).toBe(true); expect( objectsContainer .getObject('MySpriteObject') .getAllBehaviorNames() .toJSArray() ).toEqual([ 'Animation', 'Effect', 'Flippable', 'MyFirstBehavior', 'MyRenamedSecondBehavior', 'Opacity', 'Resizable', 'Scale', ]); const updatedObjectWithoutType = objectsContainer.getObject( 'MyObjectWithoutType' ); expect(updatedObjectWithoutType.getType()).toEqual('Sprite'); expect( updatedObjectWithoutType.getAllBehaviorNames().toJSArray() ).toEqual([ 'Animation', 'Effect', 'Flippable', 'Opacity', 'Resizable', 'Scale', ]); const mySpriteObject3 = objectsContainer.getObject('MySpriteObject3'); expect(mySpriteObject3.getType()).toEqual('Sprite'); expect(mySpriteObject3.getAllBehaviorNames().toJSArray()).toEqual([ 'Animation', 'Effect', 'Flippable', 'Opacity', 'Resizable', 'Scale', ]); // Verify that objects are changed in memory if changed in parameters. expect(mySpriteObject).toBe(objectsContainer.getObject('MySpriteObject')); expect(gd.getPointer(mySpriteObject)).toBe( gd.getPointer(objectsContainer.getObject('MySpriteObject')) ); // Remove an object and check that it is removed from the objects container. parameters.removeParameter('MySpriteObject'); parameters.removeParameter('MyFirstBehavior'); parameters.removeParameter('MyRenamedSecondBehavior'); // Verify changes are propagated. gd.ParameterMetadataTools.parametersToObjectsContainer( project, parameters, objectsContainer ); expect(objectsContainer.getObjectsCount()).toBe(2); expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(false); expect(objectsContainer.hasObjectNamed('MySpriteObject3')).toBe(true); // Verify that objects are still stable in memory. expect(updatedObjectWithoutType).toBe( objectsContainer.getObject('MyObjectWithoutType') ); expect(gd.getPointer(updatedObjectWithoutType)).toBe( gd.getPointer(objectsContainer.getObject('MyObjectWithoutType')) ); expect(mySpriteObject3).toBe( objectsContainer.getObject('MySpriteObject3') ); expect(gd.getPointer(mySpriteObject3)).toBe( gd.getPointer(objectsContainer.getObject('MySpriteObject3')) ); // Change an object type again and verify default behaviors are removed. parameters.getParameter('MyObjectWithoutType').setExtraInfo(''); gd.ParameterMetadataTools.parametersToObjectsContainer( project, parameters, objectsContainer ); expect(objectsContainer.getObjectsCount()).toBe(2); expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true); expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(false); expect(objectsContainer.hasObjectNamed('MySpriteObject3')).toBe(true); expect( objectsContainer .getObject('MyObjectWithoutType') .getAllBehaviorNames() .toJSArray() ).toEqual([]); objectsContainer.delete(); eventsFunction.delete(); project.delete(); }); it('can give the previous object parameter', function () { const eventsFunction = new gd.EventsFunction(); const parameters = eventsFunction.getParameters(); const parameter1 = new gd.ParameterMetadata(); parameters.insertNewParameter('Param1', 0).setType('objectList'); parameters.insertNewParameter('Param2', 1).setType('behavior'); parameters.insertNewParameter('Param3', 2).setType('objectList'); parameters.insertNewParameter('Param4', 3).setType('string'); parameters.insertNewParameter('Param5', 4).setType('objectvar'); parameters.insertNewParameter('Param6', 5).setType('objectvar'); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 0) ).toBe(0); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 1) ).toBe(0); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 2) ).toBe(2); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 3) ).toBe(2); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 4) ).toBe(2); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 5) ).toBe(2); expect( gd.ParameterMetadataTools.getObjectParameterIndexFor(parameters, 999) ).toBe(-1); eventsFunction.delete(); }); }); describe('gd.EventsFunction', () => { it('can store events', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const eventsFunction = new gd.EventsFunction(); const events = eventsFunction.getEvents(); expect(events.getEventsCount()).toBe(0); let evt = events.insertNewEvent( project, 'BuiltinCommonInstructions::Standard', 0 ); expect(events.getEventsCount()).toBe(1); eventsFunction.delete(); project.delete(); }); it('can have a name, fullname and description', function () { const eventsFunction = new gd.EventsFunction(); eventsFunction.setName('My name'); eventsFunction.setFullName('My descriptive name'); eventsFunction.setDescription('My description'); expect(eventsFunction.getName()).toBe('My name'); expect(eventsFunction.getFullName()).toBe('My descriptive name'); expect(eventsFunction.getDescription()).toBe('My description'); eventsFunction.delete(); }); it('can have a help URL', function () { const eventsFunction = new gd.EventsFunction(); expect(eventsFunction.getHelpUrl()).toBe(''); eventsFunction.setHelpUrl('https://example.com/help'); expect(eventsFunction.getHelpUrl()).toBe('https://example.com/help'); eventsFunction.setHelpUrl(''); expect(eventsFunction.getHelpUrl()).toBe(''); eventsFunction.delete(); }); }); describe('gd.EventsFunctionsExtension', () => { it('can have a namespace, version, name, fullname, description', function () { const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); eventsFunctionsExtension.setNamespace('MyExt'); eventsFunctionsExtension.setVersion('1.1'); eventsFunctionsExtension.setName('My name'); eventsFunctionsExtension.setFullName('My descriptive name'); eventsFunctionsExtension.setDescription('My description'); eventsFunctionsExtension.setIconUrl('data:image/png;base64,iVBetcetc'); eventsFunctionsExtension.setPreviewIconUrl( 'http://resources.gdevelop-app.com/test' ); expect(eventsFunctionsExtension.getNamespace()).toBe('MyExt'); expect(eventsFunctionsExtension.getVersion()).toBe('1.1'); expect(eventsFunctionsExtension.getName()).toBe('My name'); expect(eventsFunctionsExtension.getFullName()).toBe( 'My descriptive name' ); expect(eventsFunctionsExtension.getDescription()).toBe('My description'); expect(eventsFunctionsExtension.getPreviewIconUrl()).toBe( 'http://resources.gdevelop-app.com/test' ); const freeEventsFunctions = eventsFunctionsExtension.getEventsFunctions(); const eventsFunction = freeEventsFunctions.insertNewEventsFunction( 'MyFunction', 0 ); expect( freeEventsFunctions.hasEventsFunctionNamed('MyFunction') ).toBe(true); expect( freeEventsFunctions.hasEventsFunctionNamed('MyNotExistingFunction') ).toBe(false); expect(freeEventsFunctions.getEventsFunctionsCount()).toBe(1); expect(freeEventsFunctions.getEventsFunctionAt(0).getName()).toBe( 'MyFunction' ); expect( freeEventsFunctions.getEventsFunction('MyFunction').getName() ).toBe('MyFunction'); eventsFunctionsExtension.delete(); }); it('can have events based behaviors', function () { const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); expect( eventsFunctionsExtension.getEventsBasedBehaviors().getCount() ).toBe(0); eventsFunctionsExtension .getEventsBasedBehaviors() .insertNew('MyBehavior1', 0); eventsFunctionsExtension .getEventsBasedBehaviors() .insertNew('MyBehavior2', 1); expect( eventsFunctionsExtension.getEventsBasedBehaviors().getCount() ).toBe(2); expect( eventsFunctionsExtension.getEventsBasedBehaviors().has('MyBehavior1') ).toBe(true); expect( eventsFunctionsExtension.getEventsBasedBehaviors().has('MyBehavior2') ).toBe(true); expect( eventsFunctionsExtension.getEventsBasedBehaviors().has('MyBehavior3') ).toBe(false); expect( eventsFunctionsExtension.getEventsBasedBehaviors().getAt(1).getName() ).toBe('MyBehavior2'); expect( eventsFunctionsExtension .getEventsBasedBehaviors() .get('MyBehavior1') .getName() ).toBe('MyBehavior1'); }); it('can be unserialized, with tags as a (deprecated) string', function () { const project = gd.ProjectHelper.createNewGDJSProject(); const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); // Check that we can unserialize tags from the old format, where they were // written as a string. const element = gd.Serializer.fromJSObject({ version: '1.0.0', extensionNamespace: '', shortDescription: '', description: '', name: 'ExtensionName', fullName: '', tags: ' 12,34, 56 , 789', author: '', previewIconUrl: '', iconUrl: '', helpPath: '', eventsFunctions: [], eventsBasedBehaviors: [], }); eventsFunctionsExtension.unserializeFrom(project, element); element.delete(); expect(eventsFunctionsExtension.getVersion()).toBe('1.0.0'); expect(eventsFunctionsExtension.getName()).toBe('ExtensionName'); // Verify that tags were properly converted to an array expect(eventsFunctionsExtension.getTags().toJSArray()).toEqual([ '12', '34', '56', '789', ]); project.delete(); }); }); describe('gd.EventsBasedBehavior', () => { it('can have a name, fullname, description', function () { const eventsBasedBehavior = new gd.EventsBasedBehavior(); eventsBasedBehavior.setName('My name'); eventsBasedBehavior.setFullName('My descriptive name'); eventsBasedBehavior.setDescription('My description'); expect(eventsBasedBehavior.getName()).toBe('My name'); expect(eventsBasedBehavior.getFullName()).toBe('My descriptive name'); expect(eventsBasedBehavior.getDescription()).toBe('My description'); }); }); describe('gd.InstructionMetadata', () => { it('can have parameters', () => { const instructionMetadata = new gd.InstructionMetadata(); expect(instructionMetadata.getParametersCount()).toBe(0); instructionMetadata.addParameter( 'type', 'label', 'AdditionalStuffThatWillBeReplaced', false ); instructionMetadata.setParameterLongDescription('Blabla'); instructionMetadata.setParameterExtraInfo( 'AdditionalStuffLikeTypicallyAnObjectOrBehaviorType' ); expect(instructionMetadata.getParametersCount()).toBe(1); expect(instructionMetadata.getParameter(0).getType()).toBe('type'); expect(instructionMetadata.getParameter(0).getDescription()).toBe( 'label' ); expect(instructionMetadata.getParameter(0).getExtraInfo()).toBe( 'AdditionalStuffLikeTypicallyAnObjectOrBehaviorType' ); expect(instructionMetadata.getParameter(0).getLongDescription()).toBe( 'Blabla' ); }); }); describe('gd.ExpressionMetadata', () => { it('can have parameters', () => { const expressionMetadata = new gd.ExpressionMetadata( 'number', 'extensionNamespace', 'name', 'fullname', 'description', 'group', 'smallicon' ); expect(expressionMetadata.getReturnType()).toBe('number'); expect(expressionMetadata.getFullName()).toBe('fullname'); expect(expressionMetadata.getDescription()).toBe('description'); expect(expressionMetadata.getGroup()).toBe('group'); expect(expressionMetadata.getSmallIconFilename()).toBe('smallicon'); expect(expressionMetadata.isShown()).toBe(true); expect(expressionMetadata.getParametersCount()).toBe(0); expressionMetadata.addParameter('type', 'label', '', false); expressionMetadata.setParameterLongDescription('Blabla'); expect(expressionMetadata.getParametersCount()).toBe(1); expect(expressionMetadata.getParameter(0).getType()).toBe('type'); expect(expressionMetadata.getParameter(0).getDescription()).toBe('label'); expect(expressionMetadata.getParameter(0).getLongDescription()).toBe( 'Blabla' ); }); }); describe('gd.EditorSettings', () => { it('can store anything', () => { const element = gd.Serializer.fromJSObject({ test: 1, anything: { canBeStored: true, }, }); const editorSettings = new gd.EditorSettings(); editorSettings.unserializeFrom(element); const element2 = new gd.SerializerElement(); editorSettings.serializeTo(element2); expect(element2.getChild('test').getIntValue()).toBe(1); expect( element2.getChild('anything').getChild('canBeStored').getBoolValue() ).toBe(true); }); }); describe('gd.ObjectFolderOrObject (using gd.ObjectsContainer)', () => { let project = null; let layout = null; beforeAll(() => { project = gd.ProjectHelper.createNewGDJSProject(); }); afterEach(() => { project.removeLayout('Scene'); }); beforeEach(() => { layout = project.insertNewLayout('Scene', 0); }); test('objects container has a root ObjectFolderOrObject', () => { const rootFolder = layout.getObjects().getRootFolder(); expect(rootFolder.isFolder()).toBe(true); expect(rootFolder.isRootFolder()).toBe(true); expect(rootFolder.getParent().isFolder()).toBe(true); expect(rootFolder.getParent().getFolderName()).toEqual('__NULL'); expect(rootFolder.getChildrenCount()).toEqual(0); }); test('an object added to the object container is added to the root ObjectFolderOrObject', () => { let object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); const rootFolder = layout.getObjects().getRootFolder(); expect(rootFolder.hasObjectNamed('MyObject')).toBe(true); expect(rootFolder.isRootFolder()).toBe(true); expect(rootFolder.getChildrenCount()).toEqual(1); layout.getObjects().removeObject('MyObject'); expect(rootFolder.hasObjectNamed('MyObject')).toBe(false); expect(rootFolder.getChildrenCount()).toEqual(0); }); test('a folder can be added to the root folder', () => { const rootFolder = layout.getObjects().getRootFolder(); const subFolder = rootFolder.insertNewFolder('Enemies', 1); expect(subFolder.getFolderName()).toEqual('Enemies'); expect(subFolder.isRootFolder()).toBe(false); subFolder.setFolderName('Players'); expect(subFolder.getFolderName()).toEqual('Players'); expect(subFolder.getParent()).toBe(rootFolder); expect(rootFolder.getChildrenCount()).toEqual(1); }); test('an object can be added to a specific folder', () => { const rootFolder = layout.getObjects().getRootFolder(); const subFolder = rootFolder.insertNewFolder('Enemies', 0); const subSubFolder = subFolder.insertNewFolder('Turtles', 0); layout .getObjects() .insertNewObjectInFolder( project, 'Sprite', 'RedTurtle', subSubFolder, 0 ); expect(layout.getObjects().hasObjectNamed('RedTurtle')).toBe(true); expect(subSubFolder.hasObjectNamed('RedTurtle')).toBe(true); }); test('an ObjectFolderOrObject can be serialized and unserialized', () => { const rootFolder = layout.getObjects().getRootFolder(); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); const subFolder = rootFolder.insertNewFolder('Enemies', 1); const object2 = layout .getObjects() .insertNewObject(project, 'Sprite', 'OtherObject', 1); const object3 = layout .getObjects() .insertNewObject(project, 'Sprite', 'SubObject', 2); rootFolder.moveObjectFolderOrObjectToAnotherFolder( rootFolder.getObjectChild('SubObject'), subFolder, 0 ); expect(rootFolder.hasObjectNamed('MyObject')).toBe(true); expect(rootFolder.hasObjectNamed('OtherObject')).toBe(true); expect(rootFolder.getChildrenCount()).toEqual(3); expect( rootFolder.getChildPosition(rootFolder.getObjectChild('MyObject')) ).toEqual(0); expect(rootFolder.getChildPosition(subFolder)).toEqual(1); expect( rootFolder.getChildPosition(rootFolder.getObjectChild('OtherObject')) ).toEqual(2); expect(rootFolder.hasObjectNamed('SubObject')).toBe(true); expect(subFolder.hasObjectNamed('SubObject')).toBe(true); const element = new gd.SerializerElement(); layout.serializeTo(element); project.removeLayout('Scene'); const layout2 = project.insertNewLayout('Scene2', 0); layout2.unserializeFrom(project, element); expect(layout2.getObjects().hasObjectNamed('MyObject')).toBe(true); expect(layout2.getObjects().hasObjectNamed('OtherObject')).toBe(true); const rootFolder2 = layout2.getObjects().getRootFolder(); expect(rootFolder2.hasObjectNamed('MyObject')).toBe(true); expect(rootFolder2.hasObjectNamed('OtherObject')).toBe(true); expect(rootFolder2.getChildrenCount()).toEqual(3); const parentEqualities = mapFor( 0, rootFolder2.getChildrenCount(), (i) => { const childObjectFolderOrObject = rootFolder2.getChildAt(i); return childObjectFolderOrObject.getParent() === rootFolder2; } ); expect(parentEqualities.every((equality) => equality)).toBe(true); const subFolder2 = rootFolder2.getChildAt(1); expect(subFolder2.isFolder()).toBe(true); const subObject = subFolder2.getObjectChild('SubObject'); expect(subObject.getParent()).toBe(subFolder2); }); test('an ObjectFolderOrObject can be serialized and unserialized and missing object folders or objects are added', () => { const rootFolder = layout.getObjects().getRootFolder(); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); const subFolder = rootFolder.insertNewFolder('Enemies', 1); const object2 = layout .getObjects() .insertNewObject(project, 'Sprite', 'OtherObject', 1); const object3 = layout .getObjects() .insertNewObject(project, 'Sprite', 'SubObject', 2); rootFolder.moveObjectFolderOrObjectToAnotherFolder( rootFolder.getObjectChild('SubObject'), subFolder, 0 ); expect(rootFolder.hasObjectNamed('MyObject')).toBe(true); expect(rootFolder.hasObjectNamed('OtherObject')).toBe(true); expect(rootFolder.getChildrenCount()).toEqual(3); expect(rootFolder.hasObjectNamed('SubObject')).toBe(true); expect(subFolder.hasObjectNamed('SubObject')).toBe(true); const element = new gd.SerializerElement(); layout.serializeTo(element); const layoutObject = JSON.parse(gd.Serializer.toJSON(element)); delete layoutObject.objectsFolderStructure; project.removeLayout('Scene'); const layout2 = project.insertNewLayout('Scene2', 0); layout2.unserializeFrom( project, gd.Serializer.fromJSObject(layoutObject) ); expect(layout2.getObjects().hasObjectNamed('MyObject')).toBe(true); expect(layout2.getObjects().hasObjectNamed('OtherObject')).toBe(true); const rootFolder2 = layout2.getObjects().getRootFolder(); expect(rootFolder2.hasObjectNamed('MyObject')).toBe(true); expect(rootFolder2.hasObjectNamed('OtherObject')).toBe(true); expect(rootFolder2.getChildrenCount()).toEqual(3); const parentEqualities = mapFor( 0, rootFolder2.getChildrenCount(), (i) => { const childObjectFolderOrObject = rootFolder2.getChildAt(i); return childObjectFolderOrObject.getParent() === rootFolder2; } ); expect(parentEqualities.every((equality) => equality)).toBe(true); }); test('a folder can be removed from its parent if empty', () => { const rootFolder = layout.getObjects().getRootFolder(); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); let subFolder = rootFolder.insertNewFolder('Enemies', 1); const object2 = layout .getObjects() .insertNewObject(project, 'Sprite', 'OtherObject', 2); rootFolder.moveObjectFolderOrObjectToAnotherFolder( rootFolder.getObjectChild('OtherObject'), subFolder, 0 ); rootFolder.removeFolderChild(subFolder); // Check subfolder is still here since it was not empty. expect(rootFolder.getChildrenCount()).toEqual(2); subFolder = rootFolder.getChildAt(1); expect(subFolder.isFolder()).toBe(true); expect(subFolder.getChildrenCount()).toEqual(1); expect(subFolder.hasObjectNamed('OtherObject')).toBe(true); // Empty subfolder and remove it. subFolder.moveObjectFolderOrObjectToAnotherFolder( subFolder.getObjectChild('OtherObject'), rootFolder, 0 ); rootFolder.removeFolderChild(subFolder); expect(rootFolder.getChildrenCount()).toEqual(2); const objectFolderOrObject = rootFolder.getChildAt(1); const otherObjectFolderOrObject = rootFolder.getChildAt(0); expect(otherObjectFolderOrObject.isFolder()).toBe(false); expect(otherObjectFolderOrObject.isRootFolder()).toBe(false); expect(otherObjectFolderOrObject.getObject().getName()).toBe( 'OtherObject' ); expect(objectFolderOrObject.isFolder()).toBe(false); expect(objectFolderOrObject.isRootFolder()).toBe(false); expect(objectFolderOrObject.getObject().getName()).toBe('MyObject'); }); test("an ObjectFolderOrObject can test if it's a descendant of another one", () => { const rootFolder = layout.getObjects().getRootFolder(); const subFolder = rootFolder.insertNewFolder('Depth1', 0); const subSubFolder = subFolder.insertNewFolder('Depth2', 0); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); rootFolder.moveObjectFolderOrObjectToAnotherFolder( rootFolder.getObjectChild('MyObject'), subSubFolder, 0 ); const objectFolderOrObject = subSubFolder.getChildAt(0); expect(objectFolderOrObject.isFolder()).toBe(false); expect(objectFolderOrObject.getObject().getName()).toEqual('MyObject'); expect(objectFolderOrObject.isADescendantOf(subSubFolder)).toBe(true); expect(objectFolderOrObject.isADescendantOf(subFolder)).toBe(true); expect(objectFolderOrObject.isADescendantOf(rootFolder)).toBe(true); expect(subSubFolder.isADescendantOf(subFolder)).toBe(true); expect(subSubFolder.isADescendantOf(rootFolder)).toBe(true); expect(subFolder.isADescendantOf(rootFolder)).toBe(true); expect(rootFolder.isADescendantOf(objectFolderOrObject)).toBe(false); expect(rootFolder.isADescendantOf(subSubFolder)).toBe(false); expect(rootFolder.isADescendantOf(subFolder)).toBe(false); expect(rootFolder.isADescendantOf(rootFolder)).toBe(false); expect(subFolder.isADescendantOf(objectFolderOrObject)).toBe(false); expect(subFolder.isADescendantOf(subSubFolder)).toBe(false); expect(subFolder.isADescendantOf(subFolder)).toBe(false); expect(subSubFolder.isADescendantOf(objectFolderOrObject)).toBe(false); expect(subSubFolder.isADescendantOf(subSubFolder)).toBe(false); expect(objectFolderOrObject.isADescendantOf(objectFolderOrObject)).toBe( false ); }); test('an ObjectFolderOrObject representing an object can be retrieved using the object name only', () => { const rootFolder = layout.getObjects().getRootFolder(); const subFolder = rootFolder.insertNewFolder('Depth1', 0); const subSubFolder = subFolder.insertNewFolder('Depth2', 0); const object = layout .getObjects() .insertNewObject(project, 'Sprite', 'MyObject', 0); rootFolder.moveObjectFolderOrObjectToAnotherFolder( rootFolder.getObjectChild('MyObject'), subSubFolder, 0 ); const objectFolderOrObject = subSubFolder.getChildAt(0); expect(objectFolderOrObject.isRootFolder()).toBe(false); const objectFolderOrObjectFoundByName = rootFolder.getObjectNamed( 'MyObject' ); expect(objectFolderOrObjectFoundByName.isRootFolder()).toBe(false); expect(objectFolderOrObjectFoundByName).toBe(objectFolderOrObject); }); }); });