Skip to content

Commit

Permalink
Merge pull request #1029 from riganti/feature/dictionary-translations
Browse files Browse the repository at this point in the history
Added JavaScript translations for Dictionary methods
  • Loading branch information
acizmarik committed May 26, 2021
2 parents df5ccb1 + c9dc209 commit d7807a9
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 27 deletions.
21 changes: 21 additions & 0 deletions src/DotVVM.Framework.Tests/Binding/JavascriptCompilationTests.cs
Expand Up @@ -399,6 +399,27 @@ public void JsTranslator_DictionaryIndexer_Set()
Assert.AreEqual("dotvvm.dictionaryHelper.setItem(Dictionary,1,123)", result);
}

[TestMethod]
public void JsTranslator_DictionaryClear()
{
var result = CompileBinding("Dictionary.Clear()", new[] { typeof(TestViewModel5) }, typeof(void));
Assert.AreEqual("dotvvm.dictionaryHelper.clear(Dictionary)", result);
}

[TestMethod]
public void JsTranslator_DictionaryContainsKey()
{
var result = CompileBinding("Dictionary.ContainsKey(123)", new[] { typeof(TestViewModel5) }, typeof(bool));
Assert.AreEqual("dotvvm.dictionaryHelper.containsKey(Dictionary(),123)", result);
}

[TestMethod]
public void JsTranslator_DictionaryRemove()
{
var result = CompileBinding("Dictionary.Remove(123)", new[] { typeof(TestViewModel5) }, typeof(bool));
Assert.AreEqual("dotvvm.dictionaryHelper.remove(Dictionary,123)", result);
}

[TestMethod]
[DataRow("Enumerable.Where(LongArray, (long item) => item % 2 == 0)", DisplayName = "Regular call of Enumerable.Where")]
[DataRow("LongArray.Where((long item) => item % 2 == 0)", DisplayName = "Syntax sugar - extension method")]
Expand Down
Expand Up @@ -155,6 +155,7 @@ public void AddDefaultMethodTranslators()
AddDefaultToStringTranslations();
AddDefaultStringTranslations();
AddDefaultEnumerableTranslations();
AddDefaultDictionaryTranslations();
AddDefaultMathTranslations();
}

Expand Down Expand Up @@ -431,6 +432,16 @@ string GetDelegateReturnTypeHash(Type type)
AddMethodTranslator(whereMethod, translator: new GenericMethodCompiler(args => args[1].Member("filter").Invoke(args[2])));
}

private void AddDefaultDictionaryTranslations()
{
AddMethodTranslator(typeof(Dictionary<,>), "Clear", parameterCount: 0, translator: new GenericMethodCompiler(args =>
new JsIdentifierExpression("dotvvm").Member("dictionaryHelper").Member("clear").Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance))));
AddMethodTranslator(typeof(Dictionary<,>), "ContainsKey", parameterCount: 1, translator: new GenericMethodCompiler(args =>
new JsIdentifierExpression("dotvvm").Member("dictionaryHelper").Member("containsKey").Invoke(args[0], args[1])));
AddMethodTranslator(typeof(Dictionary<,>), "Remove", parameterCount: 1, translator: new GenericMethodCompiler(args =>
new JsIdentifierExpression("dotvvm").Member("dictionaryHelper").Member("remove").Invoke(args[0].WithAnnotation(ShouldBeObservableAnnotation.Instance), args[1])));
}

public JsExpression TryTranslateCall(LazyTranslatedExpression context, LazyTranslatedExpression[] args, MethodInfo method)
{
if (method == null)
Expand Down Expand Up @@ -471,8 +482,34 @@ public JsExpression TryTranslateCall(LazyTranslatedExpression context, LazyTrans

if (m2 == null)
{
m2 = genericType.GetMethod(method.Name,
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var parameters = method.GetParameters();
foreach (var m in genericType.GetMethods().Where(m => m.Name == method.Name))
{
var genParameters = m.GetParameters();
if (parameters.Length != genParameters.Length)
continue;

var isMatch = true;
for (var index = 0; index < parameters.Length; index++)
{
if (genParameters[index].ParameterType.IsGenericParameter)
{
// At this point we already know that there is no non-generic method that matches provided parameters
continue;
}
if (genParameters[index].ParameterType != parameters[index].ParameterType)
{
isMatch = false;
break;
}
}

if (isMatch)
{
m2 = m;
break;
}
}
}

if (m2 != null)
Expand Down
@@ -1,27 +1,58 @@
type Dictionary<Key, Value> = { Key: Key, Value: Value }[];

export function clear(observable: any): void {
observable.setState([]);
}

export function containsKey<Key, Value>(dictionary: Dictionary<Key, Value>, identifier: Key): boolean {
return getKeyValueIndex(dictionary, identifier) !== null;
}

export function getItem<Key, Value>(dictionary: Dictionary<Key, Value>, identifier: Key): Value {
for (let index = 0; index < dictionary.length; index++) {
let keyValuePair = ko.unwrap(dictionary[index]);
if (ko.unwrap(keyValuePair.Key) == identifier) {
return keyValuePair.Value;
}
const index = getKeyValueIndex(dictionary, identifier);
if (index === null) {
throw Error("Provided key \"" + identifier + "\" is not present in the dictionary!");
}

throw Error("Provided key \"" + identifier + "\" is not present in the dictionary!");
return dictionary[index].Value;
}

export function remove<Key, Value>(observable: any, identifier: Key): boolean {
let dictionary = [...observable.state];
const index = getKeyValueIndex(dictionary, identifier);

if (index === null) {
return false;
}
else {
dictionary.splice(index, 1);
observable.setState(dictionary);
return true;
}
}

export function setItem<Key, Value>(observable: any, identifier: Key, value: Value): void {
const dictionary = [...observable.state]
for (let index = 0; index < dictionary.length; index++) {
const dictionary = [...observable.state];
const index = getKeyValueIndex(dictionary, identifier);

if (index !== null) {
let keyValuePair = dictionary[index];
if (keyValuePair.Key == identifier) {
dictionary[index] = { Value: value, Key: keyValuePair.Key }
observable.setState(dictionary)
return;
dictionary[index] = { Key: keyValuePair.Key, Value: value };
observable.setState(dictionary);
}
else {
dictionary.push({ Key: identifier, Value: value });
observable.setState(dictionary);
}
}

function getKeyValueIndex<Key, Value>(dictionary: Dictionary<Key, Value>, identifier: Key): number | null {
for (let index = 0; index < dictionary.length; index++) {
let keyValuePair = ko.unwrap(dictionary[index]);
if (ko.unwrap(keyValuePair.Key) == identifier) {
return index;
}
}
// Create new record if we did not find provided key
dictionary.push({ "Key": identifier, "Value": value });
observable.setState(dictionary);

return null;
}
Expand Up @@ -18,14 +18,16 @@
</p>
</dot:Repeater>

<h2>Mutating operation</h2>

<h2>Operations</h2>
<p>
<span>KEY:</span> <dot:TextBox Text="{value: Key}" /> <br/>
<span>VAL:</span> <dot:TextBox Text="{value: Value}" />
<span>VAL:</span> <dot:TextBox Text="{value: Value}" /> <br/>
<span>ContainsKey</span> <dot:TextBox Text={value: Dictionary.ContainsKey(Key).ToString()} />
</p>

<dot:Button Text="Set" Click="{staticCommand: Dictionary[Key] = Value}" />
<dot:Button Text="Clear" Click="{staticCommand: Dictionary.Clear()}" />
<dot:Button Text="Remove" Click="{staticCommand: Dictionary.Remove(Key)}" />

</body>
</html>
Expand Down
63 changes: 56 additions & 7 deletions src/DotVVM.Samples.Tests/Feature/DictionaryTranslationTests.cs
Expand Up @@ -11,6 +11,55 @@ namespace DotVVM.Samples.Tests.Feature
{
public class DictionaryTranslationTests : AppSeleniumTest
{
[Fact]
public void Feature_DictionaryTranslation_Clear()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
// Clear dictionary
var inputs = browser.FindElements("input").Take(6);
inputs.Skip(4).First().Click();
var spans = browser.FindElements("span");
Assert.DoesNotContain("KEY: ", spans.First().GetText());
Assert.DoesNotContain("VAL: ", spans.Skip(1).First().GetText());
});
}

[Fact]
public void Feature_DictionaryTranslation_ContainsKey()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
var inputs = browser.FindElements("input").Take(6);
inputs.First().SendKeys("key1");
inputs.Skip(2).First().Click();
Assert.Equal("true", inputs.Skip(2).First().GetText());
inputs.First().Clear().SendKeys("key123");
inputs.Skip(2).First().Click();
Assert.Equal("false", inputs.Skip(2).First().GetText());
});
}

[Fact]
public void Feature_DictionaryTranslation_Remove()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
var inputs = browser.FindElements("input").Take(6);
inputs.First().SendKeys("key1");
inputs.Skip(5).First().Click();
var spans = browser.FindElements("span");
Assert.Equal("KEY: \"key2\"", spans.First().GetText());
Assert.Equal("VAL: \"value2\"", spans.Skip(1).First().GetText());
});
}

[Fact]
public void Feature_DictionaryTranslation_GetItem()
{
Expand All @@ -32,10 +81,10 @@ public void Feature_DictionaryTranslation_SetItem()
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
// Change value
var inputs = browser.FindElements("input").Take(3);
var inputs = browser.FindElements("input").Take(6);
inputs.First().SendKeys("key1");
inputs.Skip(1).First().SendKeys("newValue");
inputs.Skip(2).First().Click();
inputs.Skip(3).First().Click();
var spans = browser.FindElements("span");
Assert.Equal("KEY: \"key1\"", spans.First().GetText());
Expand All @@ -50,10 +99,10 @@ public void Feature_DictionaryTranslation_AddKeyValue()
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
// Create new key-value
var inputs = browser.FindElements("input").Take(3);
var inputs = browser.FindElements("input").Take(6);
inputs.First().SendKeys("key123");
inputs.Skip(1).First().SendKeys("value123");
inputs.Skip(2).First().Click();
inputs.Skip(3).First().Click();
var spans = browser.FindElements("span");
Assert.Equal("KEY: \"key123\"", spans.Skip(4).First().GetText());
Expand All @@ -68,10 +117,10 @@ public void Feature_DictionaryTranslation_AddKeyValue_ThenSetItem()
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_JavascriptTranslation_DictionaryIndexerTranslation);
// Create new key-value
var inputs = browser.FindElements("input").Take(3);
var inputs = browser.FindElements("input").Take(6);
inputs.First().SendKeys("key123");
inputs.Skip(1).First().SendKeys("value123");
inputs.Skip(2).First().Click();
inputs.Skip(3).First().Click();
var spans = browser.FindElements("span");
Assert.Equal("KEY: \"key123\"", spans.Skip(4).First().GetText());
Expand All @@ -80,7 +129,7 @@ public void Feature_DictionaryTranslation_AddKeyValue_ThenSetItem()
// Change value
inputs.First().Clear().SendKeys("key123");
inputs.Skip(1).First().Clear().SendKeys("changed-value123");
inputs.Skip(2).First().Click();
inputs.Skip(3).First().Click();
Assert.Equal("KEY: \"key123\"", spans.Skip(4).First().GetText());
Assert.Equal("VAL: \"changed-value123\"", spans.Skip(5).First().GetText());
Expand Down

0 comments on commit d7807a9

Please sign in to comment.