mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Fix exported WebManifest file having a syntax error when the game description has line breaks
Fix #4180
This commit is contained in:
@@ -929,7 +929,7 @@ const gd::String ExporterHelper::GenerateWebManifest(
|
|||||||
icons +=
|
icons +=
|
||||||
gd::String(R"({
|
gd::String(R"({
|
||||||
"src": "{FILE}",
|
"src": "{FILE}",
|
||||||
"sizes": "{SIZE}x{SIZE}"
|
"sizes": "{SIZE}x{SIZE}"
|
||||||
},)")
|
},)")
|
||||||
.FindAndReplace("{SIZE}", gd::String::From(sizeAndFile.first))
|
.FindAndReplace("{SIZE}", gd::String::From(sizeAndFile.first))
|
||||||
.FindAndReplace("{FILE}", sizeAndFile.second);
|
.FindAndReplace("{FILE}", sizeAndFile.second);
|
||||||
@@ -938,11 +938,18 @@ const gd::String ExporterHelper::GenerateWebManifest(
|
|||||||
|
|
||||||
icons = icons.RightTrim(",") + "]";
|
icons = icons.RightTrim(",") + "]";
|
||||||
|
|
||||||
|
gd::String jsonName =
|
||||||
|
gd::Serializer::ToJSON(gd::SerializerElement(project.GetName()));
|
||||||
|
gd::String jsonPackageName =
|
||||||
|
gd::Serializer::ToJSON(gd::SerializerElement(project.GetPackageName()));
|
||||||
|
gd::String jsonDescription =
|
||||||
|
gd::Serializer::ToJSON(gd::SerializerElement(project.GetDescription()));
|
||||||
|
|
||||||
return gd::String(R"webmanifest({
|
return gd::String(R"webmanifest({
|
||||||
"name": "{NAME}",
|
"name": {NAME},
|
||||||
"short_name": "{NAME}",
|
"short_name": {NAME},
|
||||||
"id": "{PACKAGE_ID}",
|
"id": {PACKAGE_ID},
|
||||||
"description": "{DESCRIPTION}",
|
"description": {DESCRIPTION},
|
||||||
"orientation": "{ORIENTATION}",
|
"orientation": "{ORIENTATION}",
|
||||||
"start_url": "./index.html",
|
"start_url": "./index.html",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
@@ -950,9 +957,9 @@ const gd::String ExporterHelper::GenerateWebManifest(
|
|||||||
"categories": ["games", "entertainment"],
|
"categories": ["games", "entertainment"],
|
||||||
"icons": {ICONS}
|
"icons": {ICONS}
|
||||||
})webmanifest")
|
})webmanifest")
|
||||||
.FindAndReplace("{NAME}", project.GetName())
|
.FindAndReplace("{NAME}", jsonName)
|
||||||
.FindAndReplace("{PACKAGE_ID}", project.GetPackageName())
|
.FindAndReplace("{PACKAGE_ID}", jsonPackageName)
|
||||||
.FindAndReplace("{DESCRIPTION}", project.GetDescription())
|
.FindAndReplace("{DESCRIPTION}", jsonDescription)
|
||||||
.FindAndReplace("{ORIENTATION}",
|
.FindAndReplace("{ORIENTATION}",
|
||||||
orientation == "default" ? "any" : orientation)
|
orientation == "default" ? "any" : orientation)
|
||||||
.FindAndReplace("{ICONS}", icons);
|
.FindAndReplace("{ICONS}", icons);
|
||||||
|
@@ -17,7 +17,17 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
|
|
||||||
describe('Exporter', () => {
|
describe('Exporter', () => {
|
||||||
// Fake files to simulate a file system (see tests below).
|
// Fake files to simulate a file system (see tests below).
|
||||||
const fakeIndexHtmlContent = '';
|
const fakeIndexHtmlContent = `<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
/* GDJS_CUSTOM_STYLE */
|
||||||
|
</style>
|
||||||
|
<!-- GDJS_CODE_FILES -->
|
||||||
|
<body>
|
||||||
|
<!-- GDJS_CUSTOM_HTML -->
|
||||||
|
</body>
|
||||||
|
</head>
|
||||||
|
</html>`;
|
||||||
const fakeConfigXmlContent = `
|
const fakeConfigXmlContent = `
|
||||||
<widget id="GDJS_PACKAGENAME" version="GDJS_PROJECTVERSION">
|
<widget id="GDJS_PACKAGENAME" version="GDJS_PROJECTVERSION">
|
||||||
<name>GDJS_PROJECTNAME</name>
|
<name>GDJS_PROJECTNAME</name>
|
||||||
@@ -38,6 +48,56 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
"author": "GDJS_GAME_AUTHOR"
|
"author": "GDJS_GAME_AUTHOR"
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
it('properly exports HTML5 files', () => {
|
||||||
|
// Create a simple project
|
||||||
|
const project = gd.ProjectHelper.createNewGDJSProject();
|
||||||
|
project.setName('My great project with spaces and "quotes"!');
|
||||||
|
project.setDescription(
|
||||||
|
'A nice project\nwith line breaks in the description.'
|
||||||
|
);
|
||||||
|
project.setVersion('1.2.3');
|
||||||
|
|
||||||
|
// Prepare a fake file system
|
||||||
|
var fs = makeFakeAbstractFileSystem(gd, {
|
||||||
|
'/fake-gdjs-root/Runtime/index.html': fakeIndexHtmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export and check the content of written files.
|
||||||
|
const exporter = new gd.Exporter(fs, '/fake-gdjs-root');
|
||||||
|
const exportOptions = new gd.MapStringBoolean();
|
||||||
|
expect(
|
||||||
|
exporter.exportWholePixiProject(
|
||||||
|
project,
|
||||||
|
'/fake-export-dir',
|
||||||
|
exportOptions
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
|
exportOptions.delete();
|
||||||
|
exporter.delete();
|
||||||
|
|
||||||
|
// Check the index.html contains the include files.
|
||||||
|
expect(fs.writeToFile.mock.calls[2][0]).toBe('/fake-export-dir/index.html');
|
||||||
|
expect(fs.writeToFile.mock.calls[2][1]).toContain('gd.js');
|
||||||
|
expect(fs.writeToFile.mock.calls[2][1]).toContain('affinetransformation.js');
|
||||||
|
expect(fs.writeToFile.mock.calls[2][1]).toContain('pixi-renderers/runtimescene-pixi-renderer.js');
|
||||||
|
expect(fs.writeToFile.mock.calls[2][1]).toContain('data.js');
|
||||||
|
|
||||||
|
// Check the webmanifest was properly generated.
|
||||||
|
expect(fs.writeToFile.mock.calls[1][0]).toBe('/fake-export-dir/manifest.webmanifest');
|
||||||
|
expect(() => JSON.parse(fs.writeToFile.mock.calls[1][1])).not.toThrow();
|
||||||
|
expect(JSON.parse(fs.writeToFile.mock.calls[1][1])).toEqual({
|
||||||
|
"name": "My great project with spaces and \"quotes\"!",
|
||||||
|
"short_name": "My great project with spaces and \"quotes\"!",
|
||||||
|
"id": "com.example.gamename",
|
||||||
|
"description": "A nice project\nwith line breaks in the description.",
|
||||||
|
"orientation": "landscape",
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "black",
|
||||||
|
"categories": ["games", "entertainment"],
|
||||||
|
"icons": []
|
||||||
|
});
|
||||||
|
});
|
||||||
it('properly exports Cordova files', () => {
|
it('properly exports Cordova files', () => {
|
||||||
// Create a simple project
|
// Create a simple project
|
||||||
const project = gd.ProjectHelper.createNewGDJSProject();
|
const project = gd.ProjectHelper.createNewGDJSProject();
|
||||||
@@ -242,10 +302,15 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
const unsupportedCapabilityAction = new gd.Instruction();
|
const unsupportedCapabilityAction = new gd.Instruction();
|
||||||
unsupportedCapabilityAction.setType('EnableEffect');
|
unsupportedCapabilityAction.setType('EnableEffect');
|
||||||
unsupportedCapabilityAction.setParametersCount(3);
|
unsupportedCapabilityAction.setParametersCount(3);
|
||||||
unsupportedCapabilityAction.setParameter(0, 'MyFakeObjectWithUnsupportedCapability');
|
unsupportedCapabilityAction.setParameter(
|
||||||
|
0,
|
||||||
|
'MyFakeObjectWithUnsupportedCapability'
|
||||||
|
);
|
||||||
unsupportedCapabilityAction.setParameter(1, '"MyEffect"');
|
unsupportedCapabilityAction.setParameter(1, '"MyEffect"');
|
||||||
unsupportedCapabilityAction.setParameter(2, 'yes');
|
unsupportedCapabilityAction.setParameter(2, 'yes');
|
||||||
gd.asStandardEvent(evt).getActions().insert(unsupportedCapabilityAction, 4);
|
gd.asStandardEvent(evt)
|
||||||
|
.getActions()
|
||||||
|
.insert(unsupportedCapabilityAction, 4);
|
||||||
|
|
||||||
const layoutCodeGenerator = new gd.LayoutCodeGenerator(project);
|
const layoutCodeGenerator = new gd.LayoutCodeGenerator(project);
|
||||||
const code = layoutCodeGenerator.generateLayoutCompleteCode(
|
const code = layoutCodeGenerator.generateLayoutCompleteCode(
|
||||||
@@ -269,7 +334,9 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
expect(code).toMatch('/* Unknown instruction - skipped. */');
|
expect(code).toMatch('/* Unknown instruction - skipped. */');
|
||||||
|
|
||||||
// Check that the action for an object not having the required capability was not generated.
|
// Check that the action for an object not having the required capability was not generated.
|
||||||
expect(code).toMatch('/* Object with unsupported capability - skipped. */');
|
expect(code).toMatch(
|
||||||
|
'/* Object with unsupported capability - skipped. */'
|
||||||
|
);
|
||||||
|
|
||||||
action.delete();
|
action.delete();
|
||||||
});
|
});
|
||||||
@@ -306,7 +373,9 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
unsupportedCapabilityAction.setParameter(0, 'MyGroup');
|
unsupportedCapabilityAction.setParameter(0, 'MyGroup');
|
||||||
unsupportedCapabilityAction.setParameter(1, '"MyEffect"');
|
unsupportedCapabilityAction.setParameter(1, '"MyEffect"');
|
||||||
unsupportedCapabilityAction.setParameter(2, 'yes');
|
unsupportedCapabilityAction.setParameter(2, 'yes');
|
||||||
gd.asStandardEvent(evt).getActions().insert(unsupportedCapabilityAction, 1);
|
gd.asStandardEvent(evt)
|
||||||
|
.getActions()
|
||||||
|
.insert(unsupportedCapabilityAction, 1);
|
||||||
|
|
||||||
const layoutCodeGenerator = new gd.LayoutCodeGenerator(project);
|
const layoutCodeGenerator = new gd.LayoutCodeGenerator(project);
|
||||||
const code = layoutCodeGenerator.generateLayoutCompleteCode(
|
const code = layoutCodeGenerator.generateLayoutCompleteCode(
|
||||||
@@ -329,7 +398,9 @@ describe('libGD.js - GDJS related tests', function () {
|
|||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
'gdjs.SceneCode.GDMySpriteObjects1[i].enableEffect("MyEffect", true);'
|
'gdjs.SceneCode.GDMySpriteObjects1[i].enableEffect("MyEffect", true);'
|
||||||
);
|
);
|
||||||
expect(code).toMatch('/* Object with unsupported capability - skipped. */');
|
expect(code).toMatch(
|
||||||
|
'/* Object with unsupported capability - skipped. */'
|
||||||
|
);
|
||||||
|
|
||||||
action.delete();
|
action.delete();
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user