Add support for setting a custom help URL for extension actions/conditions/expressions (#8103)

Only show in developer changelog
This commit is contained in:
Gleb Volkov
2026-02-10 16:04:38 +01:00
committed by GitHub
parent 12b7ed5c26
commit dd40a997d9
15 changed files with 474 additions and 21 deletions
+4
View File
@@ -75,6 +75,9 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
if (isAsync) {
element.SetBoolAttribute("async", isAsync);
}
if (!helpUrl.empty()) {
element.SetAttribute("helpUrl", helpUrl);
}
events.SerializeTo(element.AddChild("events"));
gd::String functionTypeStr = "Action";
@@ -116,6 +119,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
getterName = element.GetStringAttribute("getterName");
isPrivate = element.GetBoolAttribute("private");
isAsync = element.GetBoolAttribute("async");
helpUrl = element.GetStringAttribute("helpUrl");
events.UnserializeFrom(project, element.GetChild("events"));
gd::String functionTypeStr = element.GetStringAttribute("functionType");
+14
View File
@@ -223,6 +223,19 @@ class GD_CORE_API EventsFunction {
return *this;
}
/**
* \brief Get the help URL for this function.
*/
const gd::String& GetHelpUrl() const { return helpUrl; }
/**
* \brief Set the help URL for this function.
*/
EventsFunction& SetHelpUrl(const gd::String& helpUrl_) {
helpUrl = helpUrl_;
return *this;
}
/**
* \brief Return the events.
*/
@@ -304,6 +317,7 @@ class GD_CORE_API EventsFunction {
gd::ObjectGroupsContainer objectGroups;
bool isPrivate = false;
bool isAsync = false;
gd::String helpUrl;
};
} // namespace gd
+88
View File
@@ -0,0 +1,88 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
/**
* @file Tests covering EventsFunction
*/
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "catch.hpp"
TEST_CASE("EventsFunction", "[common]") {
SECTION("Basic properties") {
gd::EventsFunction eventsFunction;
eventsFunction.SetName("MyFunction");
eventsFunction.SetFullName("My Function");
eventsFunction.SetDescription("A test function");
eventsFunction.SetGroup("Test Group");
eventsFunction.SetSentence("Do something with _PARAM1_");
REQUIRE(eventsFunction.GetName() == "MyFunction");
REQUIRE(eventsFunction.GetFullName() == "My Function");
REQUIRE(eventsFunction.GetDescription() == "A test function");
REQUIRE(eventsFunction.GetGroup() == "Test Group");
REQUIRE(eventsFunction.GetSentence() == "Do something with _PARAM1_");
}
SECTION("Help URL") {
gd::EventsFunction eventsFunction;
// Default should be empty
REQUIRE(eventsFunction.GetHelpUrl() == "");
// Can set a help URL
eventsFunction.SetHelpUrl("https://example.com/help");
REQUIRE(eventsFunction.GetHelpUrl() == "https://example.com/help");
// Can clear the help URL
eventsFunction.SetHelpUrl("");
REQUIRE(eventsFunction.GetHelpUrl() == "");
}
SECTION("Serialization with help URL") {
gd::Project project;
gd::EventsFunction eventsFunction;
eventsFunction.SetName("MyFunction");
eventsFunction.SetFullName("My Function");
eventsFunction.SetDescription("A test function");
eventsFunction.SetHelpUrl("https://example.com/custom-help");
gd::SerializerElement element;
eventsFunction.SerializeTo(element);
gd::EventsFunction eventsFunction2;
eventsFunction2.UnserializeFrom(project, element);
REQUIRE(eventsFunction2.GetName() == "MyFunction");
REQUIRE(eventsFunction2.GetFullName() == "My Function");
REQUIRE(eventsFunction2.GetDescription() == "A test function");
REQUIRE(eventsFunction2.GetHelpUrl() == "https://example.com/custom-help");
}
SECTION("Serialization without help URL") {
gd::Project project;
gd::EventsFunction eventsFunction;
eventsFunction.SetName("MyFunction");
eventsFunction.SetFullName("My Function");
eventsFunction.SetDescription("A test function");
// No help URL set
gd::SerializerElement element;
eventsFunction.SerializeTo(element);
gd::EventsFunction eventsFunction2;
eventsFunction2.UnserializeFrom(project, element);
REQUIRE(eventsFunction2.GetName() == "MyFunction");
REQUIRE(eventsFunction2.GetFullName() == "My Function");
REQUIRE(eventsFunction2.GetDescription() == "A test function");
REQUIRE(eventsFunction2.GetHelpUrl() == "");
}
}
@@ -535,6 +535,9 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
expressionAndCondition.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
expressionAndCondition, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
}
expressionAndConditions.push_back(expressionAndCondition);
return expressionAndConditions.back();
} else {
@@ -556,6 +559,9 @@ MetadataDeclarationHelper::DeclareExpressionMetadata(
expression.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
expression, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
expression.SetHelpPath(eventsFunction.GetHelpUrl());
}
return expression;
}
}
@@ -581,6 +587,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
condition.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
condition, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
condition.SetHelpPath(eventsFunction.GetHelpUrl());
}
return condition;
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
if (freeEventsFunctions.HasEventsFunctionNamed(
@@ -607,6 +616,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
action.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
action, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
} else {
@@ -621,6 +633,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
action.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
action, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
} else {
@@ -634,6 +649,9 @@ gd::InstructionMetadata &MetadataDeclarationHelper::DeclareInstructionMetadata(
action.AddCodeOnlyParameter("currentScene", "");
DeclareEventsFunctionParameters(freeEventsFunctions, eventsFunction,
action, 0);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
}
@@ -726,6 +744,9 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
GetExtensionIconUrl(extension));
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, expressionAndCondition, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
}
expressionAndConditions.push_back(expressionAndCondition);
return expressionAndConditions.back();
} else {
@@ -751,6 +772,9 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata(
GetExtensionIconUrl(extension));
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, expression, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
expression.SetHelpPath(eventsFunction.GetHelpUrl());
}
return expression;
}
}
@@ -779,6 +803,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, condition, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
condition.SetHelpPath(eventsFunction.GetHelpUrl());
}
return condition;
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
auto &eventsFunctionsContainer = eventsBasedBehavior.GetEventsFunctions();
@@ -804,6 +831,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, action, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
} else {
auto &action = behaviorMetadata.AddScopedAction(
@@ -817,6 +847,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, action, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
} else {
@@ -834,6 +867,9 @@ MetadataDeclarationHelper::DeclareBehaviorInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedBehavior.GetEventsFunctions(),
eventsFunction, action, 2);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
}
@@ -900,6 +936,9 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, expressionAndCondition, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
expressionAndCondition.SetHelpPath(eventsFunction.GetHelpUrl());
}
expressionAndConditions.push_back(expressionAndCondition);
return expressionAndConditions.back();
} else {
@@ -926,6 +965,9 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, expression, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
expression.SetHelpPath(eventsFunction.GetHelpUrl());
}
return expression;
}
}
@@ -955,6 +997,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, condition, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
condition.SetHelpPath(eventsFunction.GetHelpUrl());
}
return condition;
} else if (functionType == gd::EventsFunction::ActionWithOperator) {
auto &eventsFunctionsContainer = eventsBasedObject.GetEventsFunctions();
@@ -979,6 +1024,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, action, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
} else {
auto &action = objectMetadata.AddScopedAction(
@@ -991,6 +1039,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, action, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
} else {
@@ -1008,6 +1059,9 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
DeclareEventsFunctionParameters(eventsBasedObject.GetEventsFunctions(),
eventsFunction, action, 1);
if (!eventsFunction.GetHelpUrl().empty()) {
action.SetHelpPath(eventsFunction.GetHelpUrl());
}
return action;
}
}
+2
View File
@@ -3206,6 +3206,8 @@ interface EventsFunction {
boolean IsPrivate();
[Ref] EventsFunction SetAsync(boolean isAsync);
boolean IsAsync();
[Ref] EventsFunction SetHelpUrl([Const] DOMString helpUrl);
[Const, Ref] DOMString GetHelpUrl();
boolean IsAction();
boolean IsExpression();
boolean IsCondition();
+9
View File
@@ -4579,6 +4579,15 @@ describe('libGD.js', function () {
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', () => {
@@ -43,6 +43,72 @@ describe('MetadataDeclarationHelper', () => {
project.delete();
});
it('propagates helpUrl to action metadata', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
const eventExtension = project.insertNewEventsFunctionsExtension(
'MyExtension',
0
);
const freeEventsFunctions = eventExtension.getEventsFunctions();
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
'MyFunction',
0
);
eventFunction.setFunctionType(gd.EventsFunction.Action);
eventFunction.setHelpUrl('https://example.com/custom-help');
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
metadataDeclarationHelper.generateFreeFunctionMetadata(
project,
extension,
eventExtension,
eventFunction
);
metadataDeclarationHelper.delete();
expect(extension.getAllActions().has('MyFunction')).toBe(true);
const action = extension.getAllActions().get('MyFunction');
expect(action.getHelpPath()).toBe('https://example.com/custom-help');
extension.delete();
project.delete();
});
it('does not set helpPath when helpUrl is empty', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
const eventExtension = project.insertNewEventsFunctionsExtension(
'MyExtension',
0
);
const freeEventsFunctions = eventExtension.getEventsFunctions();
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
'MyFunction',
0
);
eventFunction.setFunctionType(gd.EventsFunction.Action);
// No helpUrl set (empty by default)
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
metadataDeclarationHelper.generateFreeFunctionMetadata(
project,
extension,
eventExtension,
eventFunction
);
metadataDeclarationHelper.delete();
expect(extension.getAllActions().has('MyFunction')).toBe(true);
const action = extension.getAllActions().get('MyFunction');
expect(action.getHelpPath()).toBe('');
extension.delete();
project.delete();
});
it('can create metadata for free actions with an underscore and unicode characters', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
@@ -114,6 +180,78 @@ describe('MetadataDeclarationHelper', () => {
project.delete();
});
it('can create metadata for free actions with a help URL', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
const eventExtension = project.insertNewEventsFunctionsExtension(
'MyExtension',
0
);
const freeEventsFunctions = eventExtension.getEventsFunctions();
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
'MyFunction',
0
);
eventFunction.setFunctionType(gd.EventsFunction.Action);
eventFunction.setFullName('My function');
eventFunction.setDescription('My function description.');
eventFunction.setSentence('My function sentence');
eventFunction.setHelpUrl('https://example.com/help');
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
metadataDeclarationHelper.generateFreeFunctionMetadata(
project,
extension,
eventExtension,
eventFunction
);
metadataDeclarationHelper.delete();
expect(extension.getAllActions().has('MyFunction')).toBe(true);
const action = extension.getAllActions().get('MyFunction');
expect(action.getHelpPath()).toBe('https://example.com/help');
extension.delete();
project.delete();
});
it('does not set help path when help URL is empty', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
const eventExtension = project.insertNewEventsFunctionsExtension(
'MyExtension',
0
);
const freeEventsFunctions = eventExtension.getEventsFunctions();
const eventFunction = freeEventsFunctions.insertNewEventsFunction(
'MyFunction',
0
);
eventFunction.setFunctionType(gd.EventsFunction.Action);
eventFunction.setFullName('My function');
eventFunction.setDescription('My function description.');
eventFunction.setSentence('My function sentence');
// Help URL is not set (empty by default)
const metadataDeclarationHelper = new gd.MetadataDeclarationHelper();
metadataDeclarationHelper.generateFreeFunctionMetadata(
project,
extension,
eventExtension,
eventFunction
);
metadataDeclarationHelper.delete();
expect(extension.getAllActions().has('MyFunction')).toBe(true);
const action = extension.getAllActions().get('MyFunction');
expect(action.getHelpPath()).toBe('');
extension.delete();
project.delete();
});
it('can create metadata for free expressions', () => {
const extension = new gd.PlatformExtension();
const project = new gd.Project();
+2
View File
@@ -2306,6 +2306,8 @@ export class EventsFunction extends EmscriptenObject {
isPrivate(): boolean;
setAsync(isAsync: boolean): EventsFunction;
isAsync(): boolean;
setHelpUrl(helpUrl: string): EventsFunction;
getHelpUrl(): string;
isAction(): boolean;
isExpression(): boolean;
isCondition(): boolean;
+2
View File
@@ -25,6 +25,8 @@ declare class gdEventsFunction {
isPrivate(): boolean;
setAsync(isAsync: boolean): gdEventsFunction;
isAsync(): boolean;
setHelpUrl(helpUrl: string): gdEventsFunction;
getHelpUrl(): string;
isAction(): boolean;
isExpression(): boolean;
isCondition(): boolean;
@@ -24,6 +24,10 @@ import useForceUpdate from '../../Utils/UseForceUpdate';
import Checkbox from '../../UI/Checkbox';
import { type ExtensionItemConfigurationAttribute } from '../../EventsFunctionsExtensionEditor';
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
import {
isRelativePathToDocumentationRoot,
isDocumentationAbsoluteUrl,
} from '../../Utils/HelpLink';
const gd: libGDevelop = global.gd;
@@ -446,6 +450,41 @@ export const EventsFunctionPropertiesEditor = ({
/>
)}
</Line>
<Line noMargin>
{(() => {
const helpUrl = eventsFunction.getHelpUrl();
const isValidHelpUrl =
!helpUrl ||
isDocumentationAbsoluteUrl(helpUrl) ||
isRelativePathToDocumentationRoot(helpUrl);
return (
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Help page URL</Trans>}
translatableHintText={t`Enter a URL to a help page for this action/condition/expression`}
helperMarkdownText={i18n._(
t`Optional. Enter a full URL (starting with https://) to a help page. A help icon will appear next to the action/condition/expression title in the editor, allowing users to quickly access documentation.`
)}
errorText={
!isValidHelpUrl ? (
<Trans>
This is not a URL starting with "http://" or
"https://".
</Trans>
) : null
}
fullWidth
value={helpUrl}
onChange={text => {
eventsFunction.setHelpUrl(text);
if (onConfigurationUpdated)
onConfigurationUpdated();
forceUpdate();
}}
/>
);
})()}
</Line>
{type === gd.EventsFunction.ActionWithOperator ? (
<Line noMargin>
<SemiControlledTextField
@@ -16,6 +16,7 @@ import InstructionOrObjectSelector, {
} from './InstructionOrObjectSelector';
import InstructionOrExpressionSelector from './InstructionOrExpressionSelector';
import HelpButton from '../../UI/HelpButton';
import { isRelativePathToDocumentationRoot } from '../../Utils/HelpLink';
import { type EventsScope } from '../../InstructionOrExpression/EventsScope';
import { SelectColumns } from '../../UI/Responsive/SelectColumns';
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
@@ -338,9 +339,15 @@ const InstructionEditorDialog = ({
) : null,
<HelpButton
key="help"
helpPagePath={instructionHelpPage || '/events'}
helpPagePath={
instructionHelpPage &&
isRelativePathToDocumentationRoot(instructionHelpPage)
? instructionHelpPage
: '/events'
}
label={
!instructionHelpPage ||
!isRelativePathToDocumentationRoot(instructionHelpPage) ||
(isMobile || step === 'object-or-free-instructions') ? (
<Trans>Help</Trans>
) : isCondition ? (
@@ -10,6 +10,11 @@ import { mapFor } from '../../Utils/MapFor';
import EmptyMessage from '../../UI/EmptyMessage';
import ParameterRenderingService from '../ParameterRenderingService';
import HelpButton from '../../UI/HelpButton';
import HelpIcon from '../../UI/HelpIcon';
import {
isRelativePathToDocumentationRoot,
isDocumentationAbsoluteUrl,
} from '../../Utils/HelpLink';
import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource';
import { Column, Line, Spacer } from '../../UI/Grid';
import AlertMessage from '../../UI/AlertMessage';
@@ -293,8 +298,8 @@ const InstructionParametersEditor = React.forwardRef<
<I18n>
{({ i18n }) => (
<ScrollView autoHideScrollbar id={id}>
<Column expand>
<Line alignItems="flex-start">
<ColumnStackLayout expand>
<Line alignItems="flex-start" noMargin>
<img
src={iconFilename}
alt=""
@@ -306,9 +311,14 @@ const InstructionParametersEditor = React.forwardRef<
}}
/>
<Column expand>
<Text style={styles.description}>
{instructionMetadata.getDescription()}
</Text>
<Line noMargin alignItems="flex-start">
<Text style={styles.description} noMargin>
{instructionMetadata.getDescription()}
</Text>
{helpPage && isDocumentationAbsoluteUrl(helpPage) && (
<HelpIcon size="small" helpPagePath={helpPage} />
)}
</Line>
</Column>
</Line>
{instructionExtraInformation && (
@@ -464,20 +474,22 @@ const InstructionParametersEditor = React.forwardRef<
)}
</div>
<Line>
{!noHelpButton && helpPage && (
<HelpButton
helpPagePath={instructionMetadata.getHelpPath()}
label={
isCondition ? (
<Trans>Help for this condition</Trans>
) : (
<Trans>Help for this action</Trans>
)
}
/>
)}
{!noHelpButton &&
helpPage &&
isRelativePathToDocumentationRoot(helpPage) && (
<HelpButton
helpPagePath={helpPage}
label={
isCondition ? (
<Trans>Help for this condition</Trans>
) : (
<Trans>Help for this action</Trans>
)
}
/>
)}
</Line>
</Column>
</ColumnStackLayout>
</ScrollView>
)}
</I18n>
+4 -1
View File
@@ -39,7 +39,10 @@ const HelpIcon = (props: PropsType) => {
return (
<IconButton
onClick={() => Window.openExternalURL(getHelpLink(helpPagePath))}
onClick={event => {
event.stopPropagation();
Window.openExternalURL(getHelpLink(helpPagePath));
}}
disabled={props.disabled}
style={props.style}
size={props.size}
+75
View File
@@ -0,0 +1,75 @@
// @flow
import {
isRelativePathToDocumentationRoot,
isDocumentationAbsoluteUrl,
getHelpLink,
} from './HelpLink';
describe('HelpLink', () => {
describe('isRelativePathToDocumentationRoot', () => {
it('returns true for paths starting with /', () => {
expect(isRelativePathToDocumentationRoot('/test')).toBe(true);
expect(isRelativePathToDocumentationRoot('/all-features/audio')).toBe(
true
);
});
it('returns false for absolute URLs', () => {
expect(isRelativePathToDocumentationRoot('https://example.com')).toBe(
false
);
expect(isRelativePathToDocumentationRoot('http://example.com')).toBe(
false
);
});
it('returns false for empty string', () => {
expect(isRelativePathToDocumentationRoot('')).toBe(false);
});
});
describe('isDocumentationAbsoluteUrl', () => {
it('returns true for https URLs', () => {
expect(isDocumentationAbsoluteUrl('https://example.com/help')).toBe(true);
expect(isDocumentationAbsoluteUrl('https://wiki.gdevelop.io')).toBe(true);
});
it('returns true for http URLs', () => {
expect(isDocumentationAbsoluteUrl('http://example.com/help')).toBe(true);
});
it('returns false for relative paths', () => {
expect(isDocumentationAbsoluteUrl('/test')).toBe(false);
expect(isDocumentationAbsoluteUrl('/all-features/audio')).toBe(false);
});
it('returns false for empty string', () => {
expect(isDocumentationAbsoluteUrl('')).toBe(false);
});
});
describe('getHelpLink', () => {
it('returns wiki link for relative paths', () => {
expect(getHelpLink('/test')).toBe(
'https://wiki.gdevelop.io/gdevelop5/test?utm_source=gdevelop&utm_medium=help-link'
);
});
it('returns wiki link with anchor for relative paths', () => {
expect(getHelpLink('/test', 'section1')).toBe(
'https://wiki.gdevelop.io/gdevelop5/test?utm_source=gdevelop&utm_medium=help-link#section1'
);
});
it('returns the absolute URL as-is', () => {
expect(getHelpLink('https://example.com/custom-help')).toBe(
'https://example.com/custom-help'
);
});
it('returns empty string for invalid paths', () => {
expect(getHelpLink('')).toBe('');
expect(getHelpLink('invalid')).toBe('');
});
});
});
@@ -914,7 +914,11 @@ storiesOf('UI Building Blocks/HelpButton', module)
storiesOf('UI Building Blocks/HelpIcon', module)
.addDecorator(paperDecorator)
.add('default', () => <HelpIcon helpPagePath="/test" />);
.add('default (wiki path)', () => <HelpIcon helpPagePath="/test" />)
.add('absolute URL (custom help)', () => (
<HelpIcon helpPagePath="https://example.com/custom-help" />
))
.add('small size', () => <HelpIcon helpPagePath="/test" size="small" />);
storiesOf('PropertiesEditor', module)
.addDecorator(paperDecorator)