Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added JavaScript translations for Dictionary methods #1029

Merged
merged 6 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/DotVVM.Framework.Tests/Binding/JavascriptCompilationTests.cs
Original file line number Diff line number Diff line change
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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use method from arrayHelper

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this once List translations PR is merged. This will be resolved within translations refactoring PR

}

[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
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ JsExpression dictionarySetIndexer(JsExpression[] args, MethodInfo method) =>
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;
acizmarik marked this conversation as resolved.
Show resolved Hide resolved
}
if (genParameters[index].ParameterType != parameters[index].ParameterType)
{
isMatch = false;
break;
}
}

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

if (m2 != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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