Skip to content

Commit 60cba90

Browse files
committed
Allow namespaced components to be rendered server-side. Closes #37
1 parent 4c02e4e commit 60cba90

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

src/React.Tests/Core/ReactComponentTest.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class ReactComponentTest
2020
public void RenderHtmlShouldThrowExceptionIfComponentDoesNotExist()
2121
{
2222
var environment = new Mock<IReactEnvironment>();
23-
environment.Setup(x => x.HasVariable("Foo")).Returns(false);
23+
environment.Setup(x => x.Execute<bool>("typeof Foo !== 'undefined'")).Returns(false);
2424
var component = new ReactComponent(environment.Object, "Foo", "container");
2525

2626
Assert.Throws<ReactInvalidComponentException>(() =>
@@ -33,7 +33,7 @@ public void RenderHtmlShouldThrowExceptionIfComponentDoesNotExist()
3333
public void RenderHtmlShouldCallRenderComponent()
3434
{
3535
var environment = new Mock<IReactEnvironment>();
36-
environment.Setup(x => x.HasVariable("Foo")).Returns(true);
36+
environment.Setup(x => x.Execute<bool>("typeof Foo !== 'undefined'")).Returns(true);
3737

3838
var component = new ReactComponent(environment.Object, "Foo", "container")
3939
{
@@ -48,7 +48,7 @@ public void RenderHtmlShouldCallRenderComponent()
4848
public void RenderHtmlShouldWrapComponentInDiv()
4949
{
5050
var environment = new Mock<IReactEnvironment>();
51-
environment.Setup(x => x.HasVariable("Foo")).Returns(true);
51+
environment.Setup(x => x.Execute<bool>("typeof Foo !== 'undefined'")).Returns(true);
5252
environment.Setup(x => x.Execute<string>(@"React.renderComponentToString(Foo({""hello"":""World""}))"))
5353
.Returns("[HTML]");
5454

@@ -77,5 +77,25 @@ public void RenderJavaScriptShouldCallRenderComponent()
7777
result
7878
);
7979
}
80+
81+
[TestCase("Foo", true)]
82+
[TestCase("Foo.Bar", true)]
83+
[TestCase("Foo.Bar.Baz", true)]
84+
[TestCase("alert()", false)]
85+
[TestCase("Foo.alert()", false)]
86+
[TestCase("lol what", false)]
87+
public void TestEnsureComponentNameValid(string input, bool expected)
88+
{
89+
var isValid = true;
90+
try
91+
{
92+
ReactComponent.EnsureComponentNameValid(input);
93+
}
94+
catch (ReactInvalidComponentException)
95+
{
96+
isValid = false;
97+
}
98+
Assert.AreEqual(expected, isValid);
99+
}
80100
}
81101
}

src/React/ReactComponent.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10+
using System.Linq;
11+
using System.Text.RegularExpressions;
1012
using Newtonsoft.Json;
1113
using React.Exceptions;
1214

@@ -17,6 +19,13 @@ namespace React
1719
/// </summary>
1820
public class ReactComponent : IReactComponent
1921
{
22+
/// <summary>
23+
/// Regular expression used to validate JavaScript identifiers. Used to ensure component
24+
/// names are valid.
25+
/// Based off https://gist.github.com/Daniel15/3074365
26+
/// </summary>
27+
private static readonly Regex _identifierRegex = new Regex(@"^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:"".+""|\'.+\'|\d+)\])*?$", RegexOptions.Compiled);
28+
2029
/// <summary>
2130
/// Environment this component has been created in
2231
/// </summary>
@@ -45,6 +54,7 @@ public class ReactComponent : IReactComponent
4554
/// <param name="containerId">The ID of the container DIV for this component</param>
4655
public ReactComponent(IReactEnvironment environment, string componentName, string containerId)
4756
{
57+
EnsureComponentNameValid(componentName);
4858
_environment = environment;
4959
_componentName = componentName;
5060
_containerId = containerId;
@@ -89,7 +99,12 @@ public string RenderJavaScript()
8999
/// </summary>
90100
private void EnsureComponentExists()
91101
{
92-
if (!_environment.HasVariable(_componentName))
102+
// This is safe as componentName was validated via EnsureComponentNameValid()
103+
var componentExists = _environment.Execute<bool>(string.Format(
104+
"typeof {0} !== 'undefined'",
105+
_componentName
106+
));
107+
if (!componentExists)
93108
{
94109
throw new ReactInvalidComponentException(string.Format(
95110
"Could not find a component named '{0}'. Did you forget to add it to " +
@@ -112,5 +127,21 @@ private string GetComponentInitialiser()
112127
encodedProps
113128
);
114129
}
130+
131+
/// <summary>
132+
/// Validates that the specified component name is valid
133+
/// </summary>
134+
/// <param name="componentName"></param>
135+
internal static void EnsureComponentNameValid(string componentName)
136+
{
137+
var isValid = componentName.Split('.').All(segment => _identifierRegex.IsMatch(segment));
138+
if (!isValid)
139+
{
140+
throw new ReactInvalidComponentException(string.Format(
141+
"Invalid component name '{0}'",
142+
componentName
143+
));
144+
}
145+
}
115146
}
116147
}

0 commit comments

Comments
 (0)