Skip to content

Commit 2e0f84e

Browse files
[dotnet] [bidi] Align Scipt.LocalValue.Map with spec (#15395)
1 parent 18f424b commit 2e0f84e

File tree

5 files changed

+584
-8
lines changed

5 files changed

+584
-8
lines changed

dotnet/src/webdriver/BiDi/Communication/Broker.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ internal Broker(BiDi bidi, Uri url)
6464
PropertyNameCaseInsensitive = true,
6565
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
6666
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
67+
68+
// BiDi returns special numbers such as "NaN" as strings
69+
// Additionally, -0 is returned as a string "-0"
70+
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
6771
Converters =
6872
{
6973
new BrowsingContextConverter(_bidi),

dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,7 @@ public static LocalValue ConvertFrom(object? value)
7070
}
7171
}
7272

73-
public abstract record PrimitiveProtocolLocalValue : LocalValue
74-
{
75-
76-
}
73+
public abstract record PrimitiveProtocolLocalValue : LocalValue;
7774

7875
public record Number(double Value) : PrimitiveProtocolLocalValue
7976
{
@@ -103,7 +100,7 @@ public record Array(IEnumerable<LocalValue> Value) : LocalValue;
103100

104101
public record Date(string Value) : LocalValue;
105102

106-
public record Map(IDictionary<string, LocalValue> Value) : LocalValue; // seems to implement IDictionary
103+
public record Map(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue;
107104

108105
public record Object(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue;
109106

dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public abstract record RemoteValue
5555
{
5656
public static implicit operator int(RemoteValue remoteValue) => (int)((Number)remoteValue).Value;
5757
public static implicit operator long(RemoteValue remoteValue) => (long)((Number)remoteValue).Value;
58-
public static implicit operator string(RemoteValue remoteValue)
58+
public static implicit operator string?(RemoteValue remoteValue)
5959
{
6060
return remoteValue switch
6161
{
6262
String stringValue => stringValue.Value,
63-
Null => null!,
63+
Null => null,
6464
_ => throw new BiDiException($"Cannot convert {remoteValue} to string")
6565
};
6666
}
@@ -158,7 +158,7 @@ public record Map : RemoteValue
158158

159159
public InternalId? InternalId { get; set; }
160160

161-
public IDictionary<string, RemoteValue>? Value { get; set; }
161+
public IReadOnlyList<IReadOnlyList<RemoteValue>>? Value { get; set; }
162162
}
163163

164164
public record Set : RemoteValue
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
// <copyright file="CallFunctionLocalValueTest.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using NUnit.Framework;
21+
using OpenQA.Selenium.BiDi.Modules.Script;
22+
23+
namespace OpenQA.Selenium.BiDi.Script;
24+
25+
class CallFunctionLocalValueTest : BiDiTestFixture
26+
{
27+
[Test]
28+
public void CanCallFunctionWithArgumentUndefined()
29+
{
30+
var arg = new LocalValue.Undefined();
31+
Assert.That(async () =>
32+
{
33+
await context.Script.CallFunctionAsync($$"""
34+
(arg) => {
35+
if (typeof arg !== 'undefined') {
36+
throw new Error("Assert failed: " + arg);
37+
}
38+
}
39+
""", false, new() { Arguments = [arg] });
40+
}, Throws.Nothing);
41+
}
42+
43+
[Test]
44+
public void CanCallFunctionWithArgumentNull()
45+
{
46+
var arg = new LocalValue.Null();
47+
Assert.That(async () =>
48+
{
49+
await context.Script.CallFunctionAsync($$"""
50+
(arg) => {
51+
if (arg !== null) {
52+
throw new Error("Assert failed: " + arg);
53+
}
54+
}
55+
""", false, new() { Arguments = [arg] });
56+
}, Throws.Nothing);
57+
}
58+
59+
[Test]
60+
public void CanCallFunctionWithArgumentEmptyString()
61+
{
62+
var arg = new LocalValue.String(string.Empty);
63+
Assert.That(async () =>
64+
{
65+
await context.Script.CallFunctionAsync($$"""
66+
(arg) => {
67+
if (arg !== '') {
68+
throw new Error("Assert failed: " + arg);
69+
}
70+
}
71+
""", false, new() { Arguments = [arg] });
72+
}, Throws.Nothing);
73+
}
74+
75+
[Test]
76+
public void CanCallFunctionWithArgumentNonEmptyString()
77+
{
78+
var arg = new LocalValue.String("whoa");
79+
Assert.That(async () =>
80+
{
81+
await context.Script.CallFunctionAsync($$"""
82+
(arg) => {
83+
if (arg !== 'whoa') {
84+
throw new Error("Assert failed: " + arg);
85+
}
86+
}
87+
""", false, new() { Arguments = [arg] });
88+
}, Throws.Nothing);
89+
}
90+
91+
[Test]
92+
public void CanCallFunctionWithArgumentRecentDate()
93+
{
94+
const string PinnedDateTimeString = "2025-03-09T00:30:33.083Z";
95+
96+
var arg = new LocalValue.Date(PinnedDateTimeString);
97+
98+
Assert.That(async () =>
99+
{
100+
await context.Script.CallFunctionAsync($$"""
101+
(arg) => {
102+
if (arg.toISOString() !== '{{PinnedDateTimeString}}') {
103+
throw new Error("Assert failed: " + arg);
104+
}
105+
}
106+
""", false, new() { Arguments = [arg] });
107+
}, Throws.Nothing);
108+
}
109+
110+
[Test]
111+
public void CanCallFunctionWithArgumentEpochDate()
112+
{
113+
const string EpochString = "1970-01-01T00:00:00.000Z";
114+
115+
var arg = new LocalValue.Date(EpochString);
116+
117+
Assert.That(async () =>
118+
{
119+
await context.Script.CallFunctionAsync($$"""
120+
(arg) => {
121+
if (arg.toISOString() !== '{{EpochString}}') {
122+
throw new Error("Assert failed: " + arg.toISOString());
123+
}
124+
}
125+
""", false, new() { Arguments = [arg] });
126+
}, Throws.Nothing);
127+
}
128+
129+
[Test]
130+
public void CanCallFunctionWithArgumentNumberFive()
131+
{
132+
var arg = new LocalValue.Number(5);
133+
134+
Assert.That(async () =>
135+
{
136+
await context.Script.CallFunctionAsync($$"""
137+
(arg) => {
138+
if (arg !== 5) {
139+
throw new Error("Assert failed: " + arg);
140+
}
141+
}
142+
""", false, new() { Arguments = [arg] });
143+
}, Throws.Nothing);
144+
}
145+
146+
[Test]
147+
public void CanCallFunctionWithArgumentNumberNegativeFive()
148+
{
149+
var arg = new LocalValue.Number(-5);
150+
151+
Assert.That(async () =>
152+
{
153+
await context.Script.CallFunctionAsync($$"""
154+
(arg) => {
155+
if (arg !== -5) {
156+
throw new Error("Assert failed: " + arg);
157+
}
158+
}
159+
""", false, new() { Arguments = [arg] });
160+
}, Throws.Nothing);
161+
}
162+
163+
[Test]
164+
public void CanCallFunctionWithArgumentNumberZero()
165+
{
166+
var arg = new LocalValue.Number(0);
167+
168+
Assert.That(async () =>
169+
{
170+
await context.Script.CallFunctionAsync($$"""
171+
(arg) => {
172+
if (arg !== 0) {
173+
throw new Error("Assert failed: " + arg);
174+
}
175+
}
176+
""", false, new() { Arguments = [arg] });
177+
}, Throws.Nothing);
178+
}
179+
180+
[Test]
181+
[IgnoreBrowser(Selenium.Browser.Edge, "Chromium can't handle -0 argument as a number: https://github.com/w3c/webdriver-bidi/issues/887")]
182+
[IgnoreBrowser(Selenium.Browser.Chrome, "Chromium can't handle -0 argument as a number: https://github.com/w3c/webdriver-bidi/issues/887")]
183+
public void CanCallFunctionWithArgumentNumberNegativeZero()
184+
{
185+
var arg = new LocalValue.Number(double.NegativeZero);
186+
187+
Assert.That(async () =>
188+
{
189+
await context.Script.CallFunctionAsync($$"""
190+
(arg) => {
191+
if (!Object.is(arg, -0)) {
192+
throw new Error("Assert failed: " + arg.toLocaleString());
193+
}
194+
}
195+
""", false, new() { Arguments = [arg] });
196+
}, Throws.Nothing);
197+
}
198+
199+
[Test]
200+
public void CanCallFunctionWithArgumentNumberPositiveInfinity()
201+
{
202+
var arg = new LocalValue.Number(double.PositiveInfinity);
203+
204+
Assert.That(async () =>
205+
{
206+
await context.Script.CallFunctionAsync($$"""
207+
(arg) => {
208+
if (arg !== Number.POSITIVE_INFINITY) {
209+
throw new Error("Assert failed: " + arg);
210+
}
211+
}
212+
""", false, new() { Arguments = [arg] });
213+
}, Throws.Nothing);
214+
}
215+
216+
[Test]
217+
public void CanCallFunctionWithArgumentNumberNegativeInfinity()
218+
{
219+
var arg = new LocalValue.Number(double.NegativeInfinity);
220+
221+
Assert.That(async () =>
222+
{
223+
await context.Script.CallFunctionAsync($$"""
224+
(arg) => {
225+
if (arg !== Number.NEGATIVE_INFINITY) {
226+
throw new Error("Assert failed: " + arg);
227+
}
228+
}
229+
""", false, new() { Arguments = [arg] });
230+
}, Throws.Nothing);
231+
}
232+
233+
[Test]
234+
public void CanCallFunctionWithArgumentNumberNaN()
235+
{
236+
var arg = new LocalValue.Number(double.NaN);
237+
Assert.That(async () =>
238+
{
239+
await context.Script.CallFunctionAsync($$"""
240+
(arg) => {
241+
if (!isNaN(arg)) {
242+
throw new Error("Assert failed: " + arg);
243+
}
244+
}
245+
""", false, new() { Arguments = [arg] });
246+
}, Throws.Nothing);
247+
}
248+
249+
[Test]
250+
public void CanCallFunctionWithArgumentRegExp()
251+
{
252+
var arg = new LocalValue.RegExp(new LocalValue.RegExp.RegExpValue("foo*") { Flags = "g" });
253+
254+
Assert.That(async () =>
255+
{
256+
await context.Script.CallFunctionAsync($$"""
257+
(arg) => {
258+
if (!arg.test('foo') || arg.source !== 'foo*' || !arg.global) {
259+
throw new Error("Assert failed: " + arg);
260+
}
261+
}
262+
""", false, new() { Arguments = [arg] });
263+
}, Throws.Nothing);
264+
}
265+
266+
[Test]
267+
public void CanCallFunctionWithArgumentArray()
268+
{
269+
var arg = new LocalValue.Array([new LocalValue.String("hi")]);
270+
271+
Assert.That(async () =>
272+
{
273+
await context.Script.CallFunctionAsync($$"""
274+
(arg) => {
275+
if (arg.length !== 1 || arg[0] !== 'hi') {
276+
throw new Error("Assert failed: " + arg);
277+
}
278+
}
279+
""", false, new() { Arguments = [arg] });
280+
}, Throws.Nothing);
281+
}
282+
283+
[Test]
284+
public void CanCallFunctionWithArgumentObject()
285+
{
286+
var arg = new LocalValue.Object([[new LocalValue.String("objKey"), new LocalValue.String("objValue")]]);
287+
288+
Assert.That(async () =>
289+
{
290+
await context.Script.CallFunctionAsync($$"""
291+
(arg) => {
292+
if (arg.objKey !== 'objValue' || Object.keys(arg).length !== 1) {
293+
throw new Error("Assert failed: " + arg);
294+
}
295+
}
296+
""", false, new() { Arguments = [arg] });
297+
}, Throws.Nothing);
298+
}
299+
300+
[Test]
301+
public void CanCallFunctionWithArgumentMap()
302+
{
303+
var arg = new LocalValue.Map([[new LocalValue.String("mapKey"), new LocalValue.String("mapValue")]]);
304+
305+
Assert.That(async () =>
306+
{
307+
await context.Script.CallFunctionAsync($$"""
308+
(arg) => {
309+
if (arg.get('mapKey') !== 'mapValue' || arg.size !== 1) {
310+
throw new Error("Assert failed: " + arg);
311+
}
312+
}
313+
""", false, new() { Arguments = [arg] });
314+
}, Throws.Nothing);
315+
}
316+
317+
[Test]
318+
public void CanCallFunctionWithArgumentSet()
319+
{
320+
var arg = new LocalValue.Set([new LocalValue.String("setKey")]);
321+
322+
Assert.That(async () =>
323+
{
324+
await context.Script.CallFunctionAsync($$"""
325+
(arg) => {
326+
if (!arg.has('setKey') || arg.size !== 1) {
327+
throw new Error("Assert failed: " + arg);
328+
}
329+
}
330+
""", false, new() { Arguments = [arg] });
331+
}, Throws.Nothing);
332+
}
333+
}

0 commit comments

Comments
 (0)