From 88be5abe15ca1399adaec5bb4df6694ec8d970a9 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Sun, 18 Feb 2024 20:00:17 -0800 Subject: [PATCH] [Rust] Added StringBuilder.Chars --- .gitignore | 1 + src/Fable.Transforms/Dart/Fable2Dart.fs | 4 +- src/Fable.Transforms/Dart/Replacements.fs | 18 +- src/Fable.Transforms/Python/Replacements.fs | 9 +- src/Fable.Transforms/Replacements.fs | 12 +- .../Fable.Library.Dart.fsproj | 1 + src/fable-library-dart/System.Text.fs | 147 ++ src/fable-library-rust/src/System.Text.fs | 52 +- src/fable-library-ts/System.Text.fs | 58 +- tests/Dart/src/StringTests.fs | 2108 ++++++++-------- tests/Js/Main/StringTests.fs | 2142 +++++++++-------- tests/Php/TestString.fs | 84 +- tests/Python/TestString.fs | 99 +- tests/Rust/tests/src/StringTests.fs | 91 +- 14 files changed, 2628 insertions(+), 2198 deletions(-) create mode 100644 src/fable-library-dart/System.Text.fs diff --git a/.gitignore b/.gitignore index e2954c2b98..70b9f1b7d9 100644 --- a/.gitignore +++ b/.gitignore @@ -213,6 +213,7 @@ project.lock.json # Python .eggs/ __pycache__/ +.pytest_cache .python-version .hypothesis diff --git a/src/Fable.Transforms/Dart/Fable2Dart.fs b/src/Fable.Transforms/Dart/Fable2Dart.fs index 339f994503..f78bd2bcba 100644 --- a/src/Fable.Transforms/Dart/Fable2Dart.fs +++ b/src/Fable.Transforms/Dart/Fable2Dart.fs @@ -173,7 +173,7 @@ module Util = Expression.listLiteral (values, typ) let tryGetEntityIdent (com: IDartCompiler) ctx ent = - Dart.Replacements.tryEntityIdent com ent + Dart.Replacements.tryConstructor com ent |> Option.bind (fun entRef -> match transformAndCaptureExpr com ctx entRef with | [], IdentExpression ident -> Some ident @@ -187,7 +187,7 @@ module Util = match tryGetEntityIdent com ctx ent with | Some ident -> ident | None -> - addError com [] None $"Cannot find reference for {ent.FullName}" + // addError com [] None $"Cannot find reference for {ent.FullName}" makeImmutableIdent MetaType ent.DisplayName let transformTupleType com ctx genArgs = diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 10cc67003b..6edbfb2de7 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -618,7 +618,7 @@ let injectArg (com: ICompiler) (ctx: Context) r moduleName methName (genArgs: Ty |> Option.bind (injectArgInner args) |> Option.defaultValue args -let tryReplacedEntityRef (com: Compiler) entFullName = +let tryEntityIdent (com: Compiler) entFullName = match entFullName with | "Fable.Core.Dart.Future`1" -> makeIdentExpr "Future" |> Some | "Fable.Core.Dart.Stream`1" -> makeIdentExpr "Stream" |> Some @@ -683,16 +683,16 @@ let tryReplacedEntityRef (com: Compiler) entFullName = | "System.Lazy`1" -> makeImportLib com MetaType "Lazy" "FSharp.Core" |> Some | _ -> None -let tryEntityIdent com (ent: Entity) = +let tryConstructor com (ent: Entity) = if FSharp2Fable.Util.isReplacementCandidate ent.Ref then - tryReplacedEntityRef com ent.FullName + tryEntityIdent com ent.FullName else FSharp2Fable.Util.tryEntityIdentMaybeGlobalOrImported com ent -let entityIdent com ent = - match tryEntityIdent com ent with +let constructor com ent = + match tryConstructor com ent with | Some r -> r - | None -> $"Cannot find {ent.FullName} reference" |> addErrorAndReturnNull com [] None + | None -> $"Cannot find %s{ent.FullName} constructor" |> addErrorAndReturnNull com [] None let tryOp com r t op args = Helper.LibCall(com, "Option", "tryOp", t, op :: args, ?loc = r) @@ -2370,7 +2370,7 @@ let intrinsicFunctions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisAr match genArg com ctx r 0 i.GenericArgs with | DeclaredType(ent, _) -> let ent = com.GetEntity(ent) - Helper.ConstructorCall(entityIdent com ent, t, [], ?loc = r) |> Some + Helper.ConstructorCall(constructor com ent, t, [], ?loc = r) |> Some | t -> $"Cannot create instance of type unresolved at compile time: %A{t}" |> addErrorAndReturnNull com ctx.InlinePath r @@ -4113,8 +4113,8 @@ let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy) genA | _ -> None | _ -> None -let tryType = - function +let tryType typ = + match typ with | Boolean -> Some(Types.bool, parseBool, []) | Number(kind, info) -> let f = diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 33830c3fc5..29c617cbf2 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -812,10 +812,7 @@ let tryConstructor com (ent: Entity) = let constructor com ent = match tryConstructor com ent with | Some e -> e - | None -> - ent.FullName - |> sprintf "Cannot find %s constructor" - |> addErrorAndReturnNull com [] None + | None -> $"Cannot find %s{ent.FullName} constructor" |> addErrorAndReturnNull com [] None let tryOp com r t op args = Helper.LibCall(com, "option", "tryOp", t, op :: args, ?loc = r) @@ -3717,8 +3714,8 @@ let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy) genA Some(makeImportLib com Any entityName "MutableSet", args) | _ -> None -let tryType = - function +let tryType typ = + match typ with | Boolean -> Some(Types.bool, parseBool, []) | Number(kind, info) -> let f = diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index c3d3196831..2c93a3d379 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -93,7 +93,6 @@ let coreModFor = let makeDecimal com r t (x: decimal) = let str = x.ToString(System.Globalization.CultureInfo.InvariantCulture) - Helper.LibCall(com, "Decimal", "default", t, [ makeStrConst str ], isConstructor = true, ?loc = r) let makeDecimalFromExpr com r t (e: Expr) = @@ -101,7 +100,6 @@ let makeDecimalFromExpr com r t (e: Expr) = let createAtom com (value: Expr) = let typ = value.Type - Helper.LibCall(com, "Util", "createAtom", typ, [ value ], [ typ ], genArgs = [ typ ]) let getRefCell com r typ (expr: Expr) = getFieldWith r typ expr "contents" @@ -111,7 +109,6 @@ let setRefCell com r (expr: Expr) (value: Expr) = let makeRefCell com r genArg args = let typ = makeFSharpCoreType [ genArg ] Types.refCell - Helper.LibCall(com, "Types", "FSharpRef", typ, args, isConstructor = true, ?loc = r) let makeRefCellFromValue com r (value: Expr) = @@ -859,10 +856,7 @@ let tryConstructor com (ent: Entity) = let constructor com ent = match tryConstructor com ent with | Some e -> e - | None -> - ent.FullName - |> sprintf "Cannot find %s constructor" - |> addErrorAndReturnNull com [] None + | None -> $"Cannot find %s{ent.FullName} constructor" |> addErrorAndReturnNull com [] None let tryOp com r t op args = Helper.LibCall(com, "Option", "tryOp", t, op :: args, ?loc = r) @@ -4026,8 +4020,8 @@ let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy) genA | _ -> None | _ -> None -let tryType = - function +let tryType typ = + match typ with | Boolean -> Some(Types.bool, parseBool, []) | Number(kind, info) -> let f = diff --git a/src/fable-library-dart/Fable.Library.Dart.fsproj b/src/fable-library-dart/Fable.Library.Dart.fsproj index a1e6bfff63..e76d2bad7b 100644 --- a/src/fable-library-dart/Fable.Library.Dart.fsproj +++ b/src/fable-library-dart/Fable.Library.Dart.fsproj @@ -19,6 +19,7 @@ + diff --git a/src/fable-library-dart/System.Text.fs b/src/fable-library-dart/System.Text.fs new file mode 100644 index 0000000000..46f052ddb7 --- /dev/null +++ b/src/fable-library-dart/System.Text.fs @@ -0,0 +1,147 @@ +namespace System.Text + +type StringBuilder(value: string, capacity: int) = + let buf = ResizeArray(capacity) + + do + if not (System.String.IsNullOrEmpty value) then + buf.Add(value) + + new(capacity: int) = StringBuilder("", capacity) + new(value: string) = StringBuilder(value, 16) + new() = StringBuilder("", 16) + + member x.Append(s: string) = + buf.Add(s) + x + + member x.Append(s: string, startIndex: int, count: int) = + buf.Add(s.Substring(startIndex, count)) + x + + member x.Append(c: char) = + buf.Add(string c) + x + + member x.Append(o: int) = + buf.Add(string o) + x + + member x.Append(o: float) = + buf.Add(string o) + x + + member x.Append(o: bool) = + buf.Add(string o) + x + + member x.Append(o: obj) = + buf.Add(string o) + x + + member x.Append(cs: char[]) = + buf.Add(System.String(cs)) + x + + member x.Append(s: StringBuilder) = + buf.Add(s.ToString()) + x + + // member x.AppendFormat(fmt: string, o: obj) = + // buf.Add(System.String.Format(fmt, o)) + // x + + // member x.AppendFormat(fmt: string, o1: obj, o2: obj) = + // buf.Add(System.String.Format(fmt, o1, o2)) + // x + + // member x.AppendFormat(fmt: string, o1: obj, o2: obj, o3: obj) = + // buf.Add(System.String.Format(fmt, o1, o2, o3)) + // x + + // member x.AppendFormat(fmt: string, arr: obj[]) = + // buf.Add(System.String.Format(fmt, arr)) + // x + + // member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o: obj) = + // buf.Add(System.String.Format(provider, fmt, o)) + // x + + // member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o1: obj, o2: obj) = + // buf.Add(System.String.Format(provider, fmt, o1, o2)) + // x + + // member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o1: obj, o2: obj, o3: obj) = + // buf.Add(System.String.Format(provider, fmt, o1, o2, o3)) + // x + + // member x.AppendFormat(provider: System.IFormatProvider, fmt: string, arr: obj[]) = + // buf.Add(System.String.Format(provider, fmt, arr)) + // x + + member x.AppendLine() = + buf.Add(System.Environment.NewLine) + x + + member x.AppendLine(s: string) = + buf.Add(s) + buf.Add(System.Environment.NewLine) + x + + member x.Clear() = + buf.Clear() + x + + member x.Chars + + with get (index: int) = + let mutable len = 0 + let mutable i = -1 + + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then + failwith "Index was outside the bounds of the array" + else + let pos = len - index - 1 + buf[i][pos] + + and set (index: int) (value: char) = + let mutable len = 0 + let mutable i = -1 + + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then + failwith "Index was outside the bounds of the array" + else + let pos = len - index - 1 + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + + member x.Replace(oldValue: char, newValue: char) = + for i = buf.Count - 1 downto 0 do + buf[i] <- buf[i].Replace(string oldValue, string newValue) + + x + + member x.Replace(oldValue: string, newValue: string) = + let str = x.ToString().Replace(oldValue, newValue) + x.Clear().Append(str) + + member x.Length = + let mutable len = 0 + + for i = buf.Count - 1 downto 0 do + len <- len + buf[i].Length + + len + + override _.ToString() = System.String.Concat(buf) + + member x.ToString(firstIndex: int, length: int) = + let str = x.ToString() + str.Substring(firstIndex, length) diff --git a/src/fable-library-rust/src/System.Text.fs b/src/fable-library-rust/src/System.Text.fs index 5e16d52607..dbf1b7c090 100644 --- a/src/fable-library-rust/src/System.Text.fs +++ b/src/fable-library-rust/src/System.Text.fs @@ -35,8 +35,11 @@ type StringBuilder(value: string, capacity: int) = member x.Append(cs: char[]) = x.Append(System.String(cs)) member x.Append(sb: StringBuilder) = x.Append(sb.ToString()) // member x.Append(o: obj) = x.Append(string o) + + // see Replacements for AppendFormat // member x.AppendFormat(fmt: string, o: obj) = x.Append(System.String.Format(fmt, o)) // member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o: obj) = x.Append(System.String.Format(provider, fmt, o)) + member x.AppendLine() = x.Append(System.Environment.NewLine) member x.AppendLine(s: string) = x.Append(s).AppendLine() @@ -44,7 +47,54 @@ type StringBuilder(value: string, capacity: int) = buf.Clear() x - member x.Length = buf |> asArray |> Array.sumBy (fun s -> s.Length) + member x.Chars + + with get (index: int) = + let mutable len = 0 + let mutable i = -1 + + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then + failwith "Index was outside the bounds of the array" + else + let pos = len - index - 1 + buf[i][pos] + + and set (index: int) (value: char) = + let mutable len = 0 + let mutable i = -1 + + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then + failwith "Index was outside the bounds of the array" + else + let pos = len - index - 1 + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + + member x.Length = + let mutable len = 0 + + for i = buf.Count - 1 downto 0 do + len <- len + buf[i].Length + + len + + member x.Replace(oldValue: char, newValue: char) = + for i = buf.Count - 1 downto 0 do + buf[i] <- buf[i].Replace(string oldValue, string newValue) + + x + + member x.Replace(oldValue: string, newValue: string) = + let str = x.ToString().Replace(oldValue, newValue) + x.Clear().Append(str) + override _.ToString() = System.String.Concat(buf |> asArray) member x.ToString(index: int, count: int) = x.ToString().Substring(index, count) diff --git a/src/fable-library-ts/System.Text.fs b/src/fable-library-ts/System.Text.fs index 7a59e5b64a..972195dc34 100644 --- a/src/fable-library-ts/System.Text.fs +++ b/src/fable-library-ts/System.Text.fs @@ -51,10 +51,34 @@ type StringBuilder(value: string, capacity: int) = buf.Add(System.String.Format(fmt, o)) x + member x.AppendFormat(fmt: string, o1: obj, o2: obj) = + buf.Add(System.String.Format(fmt, o1, o2)) + x + + member x.AppendFormat(fmt: string, o1: obj, o2: obj, o3: obj) = + buf.Add(System.String.Format(fmt, o1, o2, o3)) + x + + member x.AppendFormat(fmt: string, arr: obj[]) = + buf.Add(System.String.Format(fmt, arr)) + x + member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o: obj) = buf.Add(System.String.Format(provider, fmt, o)) x + member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o1: obj, o2: obj) = + buf.Add(System.String.Format(provider, fmt, o1, o2)) + x + + member x.AppendFormat(provider: System.IFormatProvider, fmt: string, o1: obj, o2: obj, o3: obj) = + buf.Add(System.String.Format(provider, fmt, o1, o2, o3)) + x + + member x.AppendFormat(provider: System.IFormatProvider, fmt: string, arr: obj[]) = + buf.Add(System.String.Format(provider, fmt, arr)) + x + member x.AppendLine() = buf.Add(System.Environment.NewLine) x @@ -69,22 +93,34 @@ type StringBuilder(value: string, capacity: int) = x member x.Chars + with get (index: int) = - let bufferText = x.ToString() + let mutable len = 0 + let mutable i = -1 - if index < 0 || index >= bufferText.Length then + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - bufferText.[index] + let pos = len - index - 1 + buf[i][pos] + and set (index: int) (value: char) = - let bufferText = x.ToString() + let mutable len = 0 + let mutable i = -1 - if index < 0 || index >= bufferText.Length then + while i + 1 < buf.Count && len < index do + i <- i + 1 + len <- len + buf[i].Length + + if index < 0 || i < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let bufferText = bufferText.ToCharArray() - bufferText.[index] <- value - x.Clear().Append(bufferText) |> ignore + let pos = len - index - 1 + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] member x.Replace(oldValue: char, newValue: char) = for i = buf.Count - 1 downto 0 do @@ -93,10 +129,8 @@ type StringBuilder(value: string, capacity: int) = x member x.Replace(oldValue: string, newValue: string) = - for i = buf.Count - 1 downto 0 do - buf[i] <- buf[i].Replace(oldValue, newValue) - - x + let str = x.ToString().Replace(oldValue, newValue) + x.Clear().Append(str) member x.Length = let mutable len = 0 diff --git a/tests/Dart/src/StringTests.fs b/tests/Dart/src/StringTests.fs index 87c7fea96f..1a62e0e19d 100644 --- a/tests/Dart/src/StringTests.fs +++ b/tests/Dart/src/StringTests.fs @@ -33,27 +33,33 @@ type Test(i: int) = let containsInOrder (substrings: string list) (str: string) = let mutable lastIndex = -1 substrings |> List.forall (fun s -> - let i = str.IndexOf(s) - let success = i >= 0 && i > lastIndex - lastIndex <- i - success) + let i = str.IndexOf(s) + let success = i >= 0 && i > lastIndex + lastIndex <- i + success) let tests() = - // This test belongs to Char module, but it's the only char test we support for now - testCase "Char addition works" <| fun _ -> - 'A' + 'B' |> int |> equal 131 - 'A' + char 7 |> int |> equal 72 - // testCase "Char subtraction works" <| fun _ -> - // 'B' - 'A' |> int |> equal 1 - // char 9 - char 7 |> int |> equal 2 + testCase "F# nameof works" <| fun () -> + M.f 12 |> equal "x" + nameof M |> equal "M" + nameof M.f |> equal "f" - // TODO: Char module - // See #1628, though I'm not sure if the compiled tests are passing just the function reference without wrapping it - // testCase "Passing Char.IsDigit as a function reference doesn't make String.filter hang" <| fun () -> - // "Hello! 123" |> String.filter System.Char.IsDigit |> equal "123" + // This test belongs to Char module, but it's the only char test we support for now + testCase "Char addition works" <| fun _ -> + 'A' + 'B' |> int |> equal 131 + 'A' + char 7 |> int |> equal 72 - testCase "Strings can be indexed" <| fun () -> + // testCase "Char subtraction works" <| fun _ -> + // 'B' - 'A' |> int |> equal 1 + // char 9 - char 7 |> int |> equal 2 + + // // TODO: Char module + // // See #1628, though I'm not sure if the compiled tests are passing just the function reference without wrapping it + // testCase "Passing Char.IsDigit as a function reference doesn't make String.filter hang" <| fun () -> + // "Hello! 123" |> String.filter System.Char.IsDigit |> equal "123" + + testCase "Strings can be indexed" <| fun () -> let s = "bar" let s2 = s.ToCharArray() let s3 = [|'b'; 'a'; 'r'|] @@ -65,96 +71,258 @@ let tests() = equal c3 'r' "おはよう" |> Seq.item 2 |> equal 'よ' - testCase "String slicing works" <| fun () -> - let s = "cat and dog" - s.[2..8] |> equal "t and d" - s.[2..] |> equal "t and dog" - s.[..8] |> equal "cat and d" - - testCase "String literal addition is optimized" <| fun () -> - "bar" + aLiteral |> equal "barfoo" - "bar" + notALiteral |> equal "barfoo" - obj.ReferenceEquals("bar" + aLiteral, "barfoo") |> equal true - obj.ReferenceEquals("bar" + notALiteral, "barfoo") |> equal false - - testCase "String chunkBySize works" <| fun () -> // See #1296 - "fffff" |> Seq.chunkBySize 3 |> Seq.map String |> Seq.toList - |> equal ["fff"; "ff"] - - // TODO: StringBuilder -// testCase "StringBuilder works" <| fun () -> -// let sb = System.Text.StringBuilder() -// sb.Append "Hello" |> ignore -// sb.AppendLine () |> ignore -// sb.AppendLine "World!" |> ignore -// let expected = System.Text.StringBuilder() -// .AppendFormat("Hello{0}World!{0}", Environment.NewLine) -// .ToString() -// sb.ToString() |> equal expected -// -// testCase "StringBuilder.Lengh works" <| fun () -> -// let sb = System.Text.StringBuilder() -// sb.Append("Hello") |> ignore -// // We don't test the AppendLine for Length because depending on the OS -// // the result is different. Unix \n VS Windows \r\n -// // sb.AppendLine() |> ignore -// equal 5 sb.Length -// -// testCase "StringBuilder.ToString works with index and length" <| fun () -> -// let sb = System.Text.StringBuilder() -// sb.Append("Hello") |> ignore -// sb.AppendLine() |> ignore -// equal "ll" (sb.ToString(2, 2)) -// -// testCase "StringBuilder.Clear works" <| fun () -> -// let builder = new System.Text.StringBuilder() -// builder.Append("1111") |> ignore -// builder.Clear() |> ignore -// equal "" (builder.ToString()) -// -// testCase "StringBuilder.Append works with various overloads" <| fun () -> -// let builder = Text.StringBuilder() -// .Append(Text.StringBuilder "aaa") -// .Append("bcd".ToCharArray()) -// .Append('/') -// .Append(true) -// .Append(5.2) -// .Append(34) -// equal "aaabcd/true5.234" (builder.ToString().Replace(",", ".").ToLower()) - - // TODO: Union string formatting - // testCase "Unions with string operator" <| fun () -> - // Bar(1,5) |> string |> equal "Bar (1, 5)" - // Foo1 4.5 |> string |> equal "Foo1 4.5" - // Foo4 Foo3 |> string |> equal "Foo4 Foo3" - // Foo4(Foo1 4.5) |> string |> equal "Foo4 (Foo1 4.5)" - // Foo3 |> string |> equal "Foo3" - - testCase "F# nameof works" <| fun () -> - M.f 12 |> equal "x" - nameof M |> equal "M" - nameof M.f |> equal "f" - - testCase "string interpolation works" <| fun () -> - let name = "Phillip" - let age = 29 - $"Name: {name}, Age: %i{age}" - |> equal "Name: Phillip, Age: 29" - - testCase "string interpolation works with inline expressions" <| fun () -> - $"I think {3.0 + 0.14} is close to {Math.PI.ToString().Substring(0, 10)}!".Replace(",", ".") - |> equal "I think 3.14 is close to 3.14159265!" - - testCase "string interpolation works with anonymous records" <| fun () -> - let person = - {| Name = "John" - Surname = "Doe" - Age = 32 - Country = "The United Kingdom" |} - $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" - |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" - - testCase "Interpolated strings keep empty lines" <| fun () -> + testCase "String slicing works" <| fun () -> + let s = "cat and dog" + s.[2..8] |> equal "t and d" + s.[2..] |> equal "t and dog" + s.[..8] |> equal "cat and d" + + testCase "String literal addition is optimized" <| fun () -> + "bar" + aLiteral |> equal "barfoo" + "bar" + notALiteral |> equal "barfoo" + obj.ReferenceEquals("bar" + aLiteral, "barfoo") |> equal true + obj.ReferenceEquals("bar" + notALiteral, "barfoo") |> equal false + + testCase "String chunkBySize works" <| fun () -> // See #1296 + "fffff" |> Seq.chunkBySize 3 |> Seq.map String |> Seq.toList + |> equal ["fff"; "ff"] + + // StringBuilder + + testCase "StringBuilder works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append "Hello" |> ignore + sb.AppendLine () |> ignore + sb.AppendLine "World!" |> ignore + // let expected = System.Text.StringBuilder() + // .AppendFormat("Hello{0}World!{0}", Environment.NewLine) + // .ToString() + let expected = $"Hello{Environment.NewLine}World!{Environment.NewLine}" + sb.ToString() |> equal expected + + testCase "StringBuilder.Length works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + // We don't test the AppendLine for Length because depending on the OS + // the result is different. Unix \n VS Windows \r\n + // sb.AppendLine() |> ignore + sb.Length |> equal 5 + + testCase "StringBuilder.ToString works with index and length" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + sb.AppendLine() |> ignore + sb.ToString(2, 2) |> equal "ll" + + testCase "StringBuilder.Clear works" <| fun () -> + let sb = new System.Text.StringBuilder() + sb.Append("1111") |> ignore + sb.Clear() |> ignore + sb.ToString() |> equal "" + + testCase "StringBuilder.Append works with various overloads" <| fun () -> + let sb = System.Text.StringBuilder() + .Append(System.Text.StringBuilder "aaa") + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .Append(5.2) + .Append(34) + let actual = sb.ToString().Replace(",", ".").ToLower() + actual |> equal "aaabcd/true5.234" + + // testCase "StringBuilder.AppendFormat works" <| fun () -> + // let sb = System.Text.StringBuilder() + // sb.AppendFormat("Hello{0}World{1}", " ", "!") |> ignore + // sb.ToString() |> equal "Hello World!" + + // testCase "StringBuilder.AppendFormat with provider works" <| fun () -> + // let sb = System.Text.StringBuilder() + // sb.AppendFormat(CultureInfo.InvariantCulture, "Hello{0}World{1}", " ", "!") |> ignore + // sb.ToString() |> equal "Hello World!" + + testCase "StringBuilder.Chars works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(1) |> equal 'b' + + testCase "StringBuilder.Chars throws when index is out of bounds" <| fun () -> + throwsAnyError <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(-1) |> ignore + sb.Chars(3) |> ignore + + testCase "StringBuilder.Replace works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + .Append("abc") + .Replace('a', 'x') + .Replace("cx", "yz") + sb.ToString() |> equal "xbyzbc" + + testCase "StringBuilder index getter works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] |> equal 'b' + + testCase "StringBuilder index setter works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] <- 'x' + sb.ToString() |> equal "axc" + + // Formatting + + // testCase "kprintf works" <| fun () -> + // let f (s:string) = s + "XX" + // Printf.kprintf f "hello" |> equal "helloXX" + // Printf.kprintf f "%X" 255 |> equal "FFXX" + // Printf.kprintf f "%.2f %g" 0.5468989 5. |> equal "0.55 5XX" + + // testCase "kprintf works indirectly" <| fun () -> // See #1204 + // let lines = ResizeArray() + // let linef fmt = Printf.ksprintf lines.Add fmt // broken + // linef "open %s" "Foo" + // lines |> Seq.toList |> equal ["open Foo"] + + // testCase "kbprintf works" <| fun () -> + // let sb = System.Text.StringBuilder() + // let mutable i = 0 + // let f () = i <- i + 1 + // Printf.kbprintf f sb "Hello" + // Printf.kbprintf f sb " %s!" "world" + // i |> equal 2 + // sb.ToString() |> equal "Hello world!" + + // testCase "ksprintf curries correctly" <| fun () -> + // let append (a: string) b = a + b + // let step1 = Printf.ksprintf append "%d" + // let step2 = step1 42 + // let result = step2 "The answer is: " + // result |> equal "42The answer is: " + + // testCase "bprintf works" <| fun () -> + // let sb = System.Text.StringBuilder(10) + // Printf.bprintf sb "Hello" + // Printf.bprintf sb " %s!" "world" + // sb.ToString() |> equal "Hello world!" + + // testCase "sprintf works" <| fun () -> + // // Immediately applied + // sprintf "%.2f %g" 0.5468989 5. + // |> equal "0.55 5" + // // Curried + // let printer = sprintf "Hi %s, good %s!" + // let printer = printer "Alfonso" + // printer "morning" |> equal "Hi Alfonso, good morning!" + // printer "evening" |> equal "Hi Alfonso, good evening!" + + // testCase "sprintf works II" <| fun () -> + // let printer2 = sprintf "Hi %s, good %s%s" "Maxime" + // let printer2 = printer2 "afternoon" + // printer2 "?" |> equal "Hi Maxime, good afternoon?" + + // testCase "sprintf with different decimal digits works" <| fun () -> // See #1932 + // sprintf "Percent: %.0f%%" 5.0 |> equal "Percent: 5%" + // sprintf "Percent: %.2f%%" 5. |> equal "Percent: 5.00%" + // sprintf "Percent: %.1f%%" 5.24 |> equal "Percent: 5.2%" + // sprintf "Percent: %.2f%%" 5.268 |> equal "Percent: 5.27%" + // sprintf "Percent: %f%%" 5.67 |> equal "Percent: 5.670000%" + + // testCase "sprintf displays sign correctly" <| fun () -> // See #1937 + // sprintf "%i" 1 |> equal "1" + // sprintf "%d" 1 |> equal "1" + // sprintf "%d" 1L |> equal "1" + // sprintf "%.2f" 1. |> equal "1.00" + // sprintf "%i" -1 |> equal "-1" + // sprintf "%d" -1 |> equal "-1" + // sprintf "%d" -1L |> equal "-1" + // sprintf "%.2f" -1. |> equal "-1.00" + + // testCase "format string can use and compose string literals" <| fun () -> + // let renderedCoordinates = sprintf formatCoordinateBody 0.25 0.75 + // let renderedText = sprintf fullFormat 0.25 0.75 + + // equal "(0.250000,0.750000)" renderedCoordinates + // equal "Person at coordinates(0.250000,0.750000)" renderedText + + // testCase "Print.sprintf works" <| fun () -> // See #1216 + // let res = Printf.sprintf "%s" "abc" + // equal "res: abc" ("res: " + res) + + // testCase "sprintf without arguments works" <| fun () -> + // sprintf "hello" |> equal "hello" + + // testCase "input of print format can be retrieved" <| fun () -> + // let pathScan (pf:PrintfFormat<_,_,_,_,'t>) = + // let formatStr = pf.Value + // formatStr + + // equal "/hello/%s" (pathScan "/hello/%s") + + // testCase "sprintf with escaped percent symbols works" <| fun () -> // See #195 + // let r, r1, r2 = "Ratio", 0.213849, 0.799898 + // sprintf "%s1: %.2f%% %s2: %.2f%%" r (r1*100.) r (r2*100.) + // |> equal "Ratio1: 21.38% Ratio2: 79.99%" + + // testCase "sprintf with percent symbols in arguments works" <| fun () -> // See #329 + // let same s = sprintf "%s" s |> equal s + // same "%" + // same "%%" + // same "%%%" + // same "%%%%" + // same "% %" + // same "%% %%" + // same "%% % % %%" + + // testCase "Fix #2398: Exception when two successive string format placeholders and value of first one ends in `%`" <| fun () -> + // sprintf "%c%s" '%' "text" |> equal "%text" + + // testCase "Unions with sprintf %A" <| fun () -> + // Bar(1,5) |> sprintf "%A" |> equal "Bar (1, 5)" + // Foo1 4.5 |> sprintf "%A" |> equal "Foo1 4.5" + // Foo4 Foo3 |> sprintf "%A" |> equal "Foo4 Foo3" + // Foo4(Foo1 4.5) |> sprintf "%A" |> equal "Foo4 (Foo1 4.5)" + // Foo3 |> sprintf "%A" |> equal "Foo3" + + // testCase "Unions with string operator" <| fun () -> + // Bar(1,5) |> string |> equal "Bar (1, 5)" + // Foo1 4.5 |> string |> equal "Foo1 4.5" + // Foo4 Foo3 |> string |> equal "Foo4 Foo3" + // Foo4(Foo1 4.5) |> string |> equal "Foo4 (Foo1 4.5)" + // Foo3 |> string |> equal "Foo3" + + // testCase "sprintf \"%O\" with overloaded string works" <| fun () -> + // let o = Test(5) + // sprintf "%O" o |> equal "10" + + // testCase "sprintf \"%A\" with overloaded string works" <| fun () -> + // let o = Test(5) + // (sprintf "%A" o).Replace("\"", "") |> equal "10" + + testCase "string interpolation works" <| fun () -> + let name = "Phillip" + let age = 29 + $"Name: {name}, Age: %i{age}" + |> equal "Name: Phillip, Age: 29" + + testCase "string interpolation works with inline expressions" <| fun () -> + $"I think {3.0 + 0.14} is close to {Math.PI.ToString().Substring(0, 10)}!".Replace(",", ".") + |> equal "I think 3.14 is close to 3.14159265!" + + testCase "string interpolation works with anonymous records" <| fun () -> + let person = + {| + Name = "John" + Surname = "Doe" + Age = 32 + Country = "The United Kingdom" + |} + $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" + |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" + + testCase "Interpolated strings keep empty lines" <| fun () -> let s1 = $"""1 @@ -173,932 +341,810 @@ let tests() = equal s1.Length s2.Length equal 13 s1.Length - testCase "Can use backslash is interpolated strings" <| fun () -> + testCase "Can use backslash is interpolated strings" <| fun () -> $"(\n{1+1}\n)" |> equal """( 2 )""" - testCase "Backslash is escaped in interpolated strings" <| fun () -> // See #2649 - $"\\" |> equal @"\" - $"\\".Length |> equal 1 - $@"\" |> equal @"\" - $@"\".Length |> equal 1 - @$"\" |> equal @"\" - @$"\".Length |> equal 1 - $"\\{4}" |> equal @"\4" - $"\\{4}".Length |> equal 2 - $@"\{4}" |> equal @"\4" - $@"\{4}".Length |> equal 2 - @$"\{4}" |> equal @"\4" - @$"\{4}".Length |> equal 2 - - // TODO: Formatting -// testCase "kprintf works" <| fun () -> -// let f (s:string) = s + "XX" -// Printf.kprintf f "hello" |> equal "helloXX" -// Printf.kprintf f "%X" 255 |> equal "FFXX" -// Printf.kprintf f "%.2f %g" 0.5468989 5. |> equal "0.55 5XX" -// -// testCase "kprintf works indirectly" <| fun () -> // See #1204 -// let lines = ResizeArray() -// let linef fmt = Printf.ksprintf lines.Add fmt // broken -// linef "open %s" "Foo" -// lines |> Seq.toList |> equal ["open Foo"] -// -// testCase "kbprintf works" <| fun () -> -// let sb = System.Text.StringBuilder() -// let mutable i = 0 -// let f () = i <- i + 1 -// Printf.kbprintf f sb "Hello" -// Printf.kbprintf f sb " %s!" "world" -// i |> equal 2 -// sb.ToString() |> equal "Hello world!" -// -// testCase "ksprintf curries correctly" <| fun () -> -// let append (a: string) b = a + b -// -// let step1 = Printf.ksprintf append "%d" -// let step2 = step1 42 -// let result = step2 "The answer is: " -// result |> equal "42The answer is: " -// -// testCase "bprintf works" <| fun () -> -// let sb = System.Text.StringBuilder(10) -// Printf.bprintf sb "Hello" -// Printf.bprintf sb " %s!" "world" -// sb.ToString() |> equal "Hello world!" -// -// testCase "sprintf works" <| fun () -> -// // Immediately applied -// sprintf "%.2f %g" 0.5468989 5. -// |> equal "0.55 5" -// // Curried -// let printer = sprintf "Hi %s, good %s!" -// let printer = printer "Alfonso" -// printer "morning" |> equal "Hi Alfonso, good morning!" -// printer "evening" |> equal "Hi Alfonso, good evening!" -// -// testCase "sprintf works II" <| fun () -> -// let printer2 = sprintf "Hi %s, good %s%s" "Maxime" -// let printer2 = printer2 "afternoon" -// printer2 "?" |> equal "Hi Maxime, good afternoon?" -// -// testCase "sprintf with different decimal digits works" <| fun () -> // See #1932 -// sprintf "Percent: %.0f%%" 5.0 |> equal "Percent: 5%" -// sprintf "Percent: %.2f%%" 5. |> equal "Percent: 5.00%" -// sprintf "Percent: %.1f%%" 5.24 |> equal "Percent: 5.2%" -// sprintf "Percent: %.2f%%" 5.268 |> equal "Percent: 5.27%" -// sprintf "Percent: %f%%" 5.67 |> equal "Percent: 5.670000%" - -// testCase "sprintf with double % should be unescaped" <| fun () -> -// sprintf "%d%%" 100 |> equal "100%" -// -// testCase "sprintf displays sign correctly" <| fun () -> // See #1937 -// sprintf "%i" 1 |> equal "1" -// sprintf "%d" 1 |> equal "1" -// sprintf "%d" 1L |> equal "1" -// sprintf "%.2f" 1. |> equal "1.00" -// sprintf "%i" -1 |> equal "-1" -// sprintf "%d" -1 |> equal "-1" -// sprintf "%d" -1L |> equal "-1" -// sprintf "%.2f" -1. |> equal "-1.00" -// -// testCase "format string can use and compose string literals" <| fun () -> -// let renderedCoordinates = sprintf formatCoordinateBody 0.25 0.75 -// let renderedText = sprintf fullFormat 0.25 0.75 -// -// equal "(0.250000,0.750000)" renderedCoordinates -// equal "Person at coordinates(0.250000,0.750000)" renderedText -// -// testCase "Print.sprintf works" <| fun () -> // See #1216 -// let res = Printf.sprintf "%s" "abc" -// equal "res: abc" ("res: " + res) -// -// testCase "sprintf without arguments works" <| fun () -> -// sprintf "hello" |> equal "hello" -// -// testCase "input of print format can be retrieved" <| fun () -> -// let pathScan (pf:PrintfFormat<_,_,_,_,'t>) = -// let formatStr = pf.Value -// formatStr -// -// equal "/hello/%s" (pathScan "/hello/%s") -// -// testCase "sprintf with escaped percent symbols works" <| fun () -> // See #195 -// let r, r1, r2 = "Ratio", 0.213849, 0.799898 -// sprintf "%s1: %.2f%% %s2: %.2f%%" r (r1*100.) r (r2*100.) -// |> equal "Ratio1: 21.38% Ratio2: 79.99%" -// -// testCase "sprintf with percent symbols in arguments works" <| fun () -> // See #329 -// let same s = sprintf "%s" s |> equal s -// same "%" -// same "%%" -// same "%%%" -// same "%%%%" -// same "% %" -// same "%% %%" -// same "%% % % %%" -// -// testCase "Fix #2398: Exception when two successive string format placeholders and value of first one ends in `%`" <| fun () -> -// sprintf "%c%s" '%' "text" |> equal "%text" -// -// testCase "Unions with sprintf %A" <| fun () -> -// Bar(1,5) |> sprintf "%A" |> equal "Bar (1, 5)" -// Foo1 4.5 |> sprintf "%A" |> equal "Foo1 4.5" -// Foo4 Foo3 |> sprintf "%A" |> equal "Foo4 Foo3" -// Foo4(Foo1 4.5) |> sprintf "%A" |> equal "Foo4 (Foo1 4.5)" -// Foo3 |> sprintf "%A" |> equal "Foo3" -// -// testCase "sprintf \"%O\" with overloaded string works" <| fun () -> -// let o = Test(5) -// sprintf "%O" o |> equal "10" -// -// testCase "sprintf \"%A\" with overloaded string works" <| fun () -> -// let o = Test(5) -// (sprintf "%A" o).Replace("\"", "") |> equal "10" -// -// testCase "Extended string interpolation syntax" <| fun () -> -// let classAttr = "item-panel" -// let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""// -// cssNew |> equal ".item-panel:hover {background-color: #eee;}" -// -// testCase "sprintf \"%A\" with lists works" <| fun () -> -// let xs = ["Hi"; "Hello"; "Hola"] -// (sprintf "%A" xs).Replace("\"", "") |> equal "[Hi; Hello; Hola]" -// -// testCase "sprintf \"%A\" with nested lists works" <| fun () -> -// let xs = [["Hi"]; ["Hello"]; ["Hola"]] -// (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" -// -// testCase "sprintf \"%A\" with sequences works" <| fun () -> -// let xs = seq { "Hi"; "Hello"; "Hola" } -// sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true -// -// testCase "Storing result of Seq.tail and printing the result several times works. Related to #1996" <| fun () -> -// let tweets = seq { "Hi"; "Hello"; "Hola" } -// let tweetsTailR: seq = tweets |> Seq.tail -// -// let a = sprintf "%A" (tweetsTailR) -// let b = sprintf "%A" (tweetsTailR) -// -// containsInOrder ["Hello"; "Hola"] a |> equal true -// containsInOrder ["Hello"; "Hola"] b |> equal true -// -// testCase "sprintf \"%X\" works" <| fun () -> -// //These should all be the Native JS Versions (except int64 / uint64) -// //See #1530 for more information. -// -// sprintf "255: %X" 255 |> equal "255: FF" -// sprintf "255: %x" 255 |> equal "255: ff" -// sprintf "-255: %X" -255 |> equal "-255: FFFFFF01" -// sprintf "4095L: %X" 4095L |> equal "4095L: FFF" -// sprintf "-4095L: %X" -4095L |> equal "-4095L: FFFFFFFFFFFFF001" -// sprintf "1 <<< 31: %x" (1 <<< 31) |> equal "1 <<< 31: 80000000" -// sprintf "1u <<< 31: %x" (1u <<< 31) |> equal "1u <<< 31: 80000000" -// sprintf "2147483649L: %x" 2147483649L |> equal "2147483649L: 80000001" -// sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" -// sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" -// sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" -// -// testCase "sprintf integers with sign and padding works" <| fun () -> // See #1931 -// sprintf "%+04i" 1 |> equal "+001" -// sprintf "%+04i" -1 |> equal "-001" -// sprintf "%5d" -5 |> equal " -5" -// sprintf "%5d" -5L |> equal " -5" -// sprintf "%- 4i" 5 |> equal " 5 " -// -// testCase "parameterized padding works" <| fun () -> // See #2336 -// sprintf "[%*s][%*s]" 6 "Hello" 5 "Foo" -// |> equal "[ Hello][ Foo]" -// -// testCase "String.Format should fail if there are less arguments than placeholders" <| fun () -> // See #2768 -// throwsAnyError <| fun () -> String.Format ("Hello {0}", args = Array.empty) -// -// testCase "String.Format combining padding and zeroes pattern works" <| fun () -> -// String.Format(CultureInfo.InvariantCulture, "{0:++0.00++}", -5000.5657) |> equal "-++5000.57++" -// String.Format(CultureInfo.InvariantCulture, "{0:000.00}foo", 5) |> equal "005.00foo" -// String.Format(CultureInfo.InvariantCulture, "{0,-8:000.00}foo", 12.456) |> equal "012.46 foo" -// -// testCase "String.Format {0:x} works" <| fun () -> -// //See above comment on expected values -// String.Format(CultureInfo.InvariantCulture, "255: {0:X}", 255) |> equal "255: FF" -// String.Format(CultureInfo.InvariantCulture, "255: {0:x}", 255) |> equal "255: ff" -// String.Format(CultureInfo.InvariantCulture, "-255: {0:X}", -255) |> equal "-255: FFFFFF01" -// String.Format(CultureInfo.InvariantCulture, "4095L: {0:X}", 4095L) |> equal "4095L: FFF" -// String.Format(CultureInfo.InvariantCulture, "-4095L: {0:X}", -4095L) |> equal "-4095L: FFFFFFFFFFFFF001" -// String.Format(CultureInfo.InvariantCulture, "1 <<< 31: {0:x}", (1 <<< 31)) |> equal "1 <<< 31: 80000000" -// String.Format(CultureInfo.InvariantCulture, "1u <<< 31: {0:x}", (1u <<< 31)) |> equal "1u <<< 31: 80000000" -// String.Format(CultureInfo.InvariantCulture, "2147483649L: {0:x}", 2147483649L) |> equal "2147483649L: 80000001" -// String.Format(CultureInfo.InvariantCulture, "2147483650uL: {0:x}", 2147483650uL) |> equal "2147483650uL: 80000002" -// String.Format(CultureInfo.InvariantCulture, "1L <<< 63: {0:x}", (1L <<< 63)) |> equal "1L <<< 63: 8000000000000000" -// String.Format(CultureInfo.InvariantCulture, "1uL <<< 63: {0:x}", (1uL <<< 63)) |> equal "1uL <<< 63: 8000000000000000" -// -// testCase "String.Format {0:x} with precision works" <| fun () -> -// String.Format(CultureInfo.InvariantCulture, "#{0:X3}", 0xC149D) |> equal "#C149D" -// String.Format(CultureInfo.InvariantCulture, "#{0:X6}", 0xC149D) |> equal "#0C149D" -// -// testCase "String.Format works with thousands separator" <| fun () -> -// String.Format(CultureInfo.InvariantCulture, "{0}", 12343235354.6547757) |> equal "12343235354.654776" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757) |> equal "12,343,235,354.655" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757M) |> equal "12,343,235,354.655" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456) |> equal "123.46" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456M) |> equal "123.46" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123438192123.456M) |> equal "123,438,192,123.46" -// String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 1.456M) |> equal "1" -// String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1.456M) |> equal "01" -// -// testCase "String.Format can omit decimal digits" <| fun () -> -// String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 12343235354.6547757) |> equal "12,343,235,355" -// String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.6547757) |> equal "12,343,235,355" -// String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.) |> equal "12,343,235,354" -// String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354.) |> equal "12343235354" -// String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354.) |> equal "12343235354" -// -// String.Format(CultureInfo.InvariantCulture, "{0:#,#}", 12343235354.6547757M) |> equal "12,343,235,355" -// String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354.6547757M) |> equal "12,343,235,355" -// -// String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1234323535) |> equal "1,234,323,535" -// String.Format(CultureInfo.InvariantCulture, "{0:#}", 1234323535) |> equal "1234323535" -// String.Format(CultureInfo.InvariantCulture, "{0:0}", 1234323535) |> equal "1234323535" -// -// String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354M) |> equal "12,343,235,354" -// String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 343235354M) |> equal "343,235,354" -// String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354M) |> equal "12343235354" -// String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354M) |> equal "12343235354" -// -// testCase "ToString formatted works with decimals" <| fun () -> // See #2276 -// let decimal = 78.6M -// decimal.ToString("0.000").Replace(",", ".") |> equal "78.600" -// -// testCase "Printf works with generic argument" <| fun () -> -// spr "bar %s" "a" |> equal "bar a" -// spr "foo %i %i" 3 5 |> equal "foo 3 5" -// let f1 = spr "foo %i %i" -// let f2 = f1 2 -// f2 2 |> equal "foo 2 2" -// let f1 = spr "foo %i %i %i" -// let f2 = f1 2 -// let f3 = f2 2 -// f3 2 |> equal "foo 2 2 2" -// -// testCase "Printf in sequence is not erased" <| fun () -> -// let x = sprintf "Foo" -// let y = sprintf "B%sr" "a" -// x + y |> equal "FooBar" -// -// testCase "String.Format works" <| fun () -> -// let arg1, arg2, arg3 = "F#", "Fable", "Babel" -// String.Format(CultureInfo.InvariantCulture, "{2} is to {1} what {1} is to {0}", arg1, arg2, arg3) -// |> equal "Babel is to Fable what Fable is to F#" -// -// testCase "String.Format with extra formatting works" <| fun () -> -// let i = 0.5466788 -// let dt = DateTime(2014, 9, 26).AddMinutes(19.) -// String.Format(CultureInfo.InvariantCulture, "{0:F2} {0:P2} {1:yyyy-MM-dd HH:mm}", i, dt) -// .Replace(",", ".").Replace(" %", "%") -// |> equal "0.55 54.67% 2014-09-26 00:19" -// -// testCase "Padding works" <| fun () -> -// "3.14".PadLeft(10) |> equal " 3.14" -// "3.14".PadRight(10) |> equal "3.14 " -// "22".PadLeft(10, '0') |> equal "0000000022" -// "-22".PadRight(10, 'X') |> equal "-22XXXXXXX" -// "333".PadLeft(1) |> equal "333" -// -// testCase "Padding with sprintf works" <| fun () -> -// sprintf "%10.1f" 3.14 |> equal " 3.1" -// sprintf "%-10.1f" 3.14 |> equal "3.1 " -// sprintf "%+010i" 22 |> equal "+000000022" -// sprintf "%+0-10i" -22 |> equal "-22 " -// -// testCase "Padding with String.Format works" <| fun () -> -// String.Format(CultureInfo.InvariantCulture, "{0,10:F1}", 3.14) |> equal " 3.1" -// String.Format(CultureInfo.InvariantCulture, "{0,-10:F1}", 3.14) |> equal "3.1 " -// String.Format(CultureInfo.InvariantCulture, "{0,10}", 22) |> equal " 22" -// String.Format(CultureInfo.InvariantCulture, "{0,-10}", -22) |> equal "-22 " - - // Conversions - - testCase "Conversion char to int works" <| fun () -> - equal 97 (int 'a') - equal 'a' (char 97) - - testCase "Conversion string to char works" <| fun () -> - equal 'a' (char "a") - equal "a" (string 'a') - - testCase "Conversion string to negative int8 works" <| fun () -> - equal -5y (int8 "-5") - equal "-5" (string -5y) - - testCase "Conversion string to negative int16 works" <| fun () -> - equal -5s (int16 "-5") - equal "-5" (string -5s) - - testCase "Conversion string to negative int32 works" <| fun () -> - equal -5 (int32 "-5") - equal "-5" (string -5) - - testCase "Conversion string to negative int64 works" <| fun () -> - equal -5L (int64 "-5") - equal "-5" (string -5L) - - testCase "Conversion string to int8 works" <| fun () -> - equal 5y (int8 "5") - equal "5" (string 5y) - - testCase "Conversion string to int16 works" <| fun () -> - equal 5s (int16 "5") - equal "5" (string 5s) - - testCase "Conversion string to int32 works" <| fun () -> - equal 5 (int32 "5") - equal "5" (string 5) - - testCase "Conversion string to int64 works" <| fun () -> - equal 5L (int64 "5") - equal "5" (string 5L) - - testCase "Conversion string to uint8 works" <| fun () -> - equal 5uy (uint8 "5") - equal "5" (string 5uy) - - testCase "Conversion string to uint16 works" <| fun () -> - equal 5us (uint16 "5") - equal "5" (string 5us) - - testCase "Conversion string to uint32 works" <| fun () -> - equal 5u (uint32 "5") - equal "5" (string 5u) - - testCase "Conversion string to uint64 works" <| fun () -> - equal 5uL (uint64 "5") - equal "5" (string 5uL) - - testCase "Conversion string to single works" <| fun () -> - equal 5.f (float32 "5.0") - equal -5.f (float32 "-5.0") - (string 5.f).StartsWith("5") |> equal true - equal 5.25f (float32 "5.25") - (string 5.25f).StartsWith("5.25") |> equal true - - testCase "Conversion string to double works" <| fun () -> - equal 5. (float "5.0") - equal -5. (float "-5.0") - (string 5.).StartsWith("5") |> equal true - equal 5.25 (float "5.25") - (string 5.25).StartsWith("5.25") |> equal true - - // testCase "Conversion string to decimal works" <| fun () -> - // equal 5.m (decimal "5.0") - // equal -5.m (decimal "-5.0") - // (string 5.m).StartsWith("5") |> equal true - // equal 5.25m (decimal "5.25") - // (string 5.25m).StartsWith("5.25") |> equal true - - // System.String - constructors - - testCase "String.ctor(char[]) works" <| fun () -> - System.String([|'f'; 'a'; 'b'; 'l'; 'e'|]) - |> equal "fable" - - testCase "String.ctor(char, int) works" <| fun () -> - System.String('f', 5) - |> equal "fffff" - - testCase "String.ctor(char[], int, int) works" <| fun () -> - System.String([|'f'; 'a'; 'b'; 'l'; 'e'|], 1, 3) - |> equal "abl" - - // System.String - static methods - - testCase "System.String.Equals works" <| fun () -> - System.String.Equals("abc", "abc") |> equal true - System.String.Equals("ABC", "abc") |> equal false - System.String.Equals("abc", "abd") |> equal false - "abc".Equals("abc") |> equal true - "ABC".Equals("abc") |> equal false - "abc".Equals("abd") |> equal false - System.String.Equals("ABC", "abc", StringComparison.Ordinal) |> equal false - System.String.Equals("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal true - "ABC".Equals("abc", StringComparison.Ordinal) |> equal false - "ABC".Equals("abc", StringComparison.OrdinalIgnoreCase) |> equal true - - // TODO: Dart seems to compare lower/uppercase strings differently as how .NET does - testCase "String.Compare works" <| fun () -> - // "ABC".CompareTo("abc") > 0 |> equal true - // System.String.Compare("abc", "abc") |> equal 0 - // System.String.Compare("ABC", "abc") |> equal 1 - // System.String.Compare("abc", "abd") |> equal -1 - // System.String.Compare("bbc", "abd") |> equal 1 - // System.String.Compare("ABC", "abc", false) |> equal 1 - System.String.Compare("ABC", "abc", true) |> equal 0 - System.String.Compare("ABC", "abd", true) |> equal -1 - System.String.Compare("BBC", "abd", true) |> equal 1 - // System.String.Compare("ABC", "abc", StringComparison.CurrentCulture) > 0 |> equal true - // System.String.Compare("ABC", "abc", StringComparison.Ordinal) < 0 |> equal true - System.String.Compare("ABC", "abc", StringComparison.CurrentCultureIgnoreCase) |> equal 0 - System.String.Compare("ABC", "abc", StringComparison.InvariantCultureIgnoreCase) |> equal 0 - System.String.Compare("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal 0 - // System.String.Compare("abc", 0, "b", 0, 3) |> equal -1 - // System.String.Compare("abc", 1, "bcd", 0, 2) |> equal 0 - // System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCulture) > 0 |> equal true - // System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.Ordinal) < 0 |> equal true - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCultureIgnoreCase) |> equal 0 - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.InvariantCultureIgnoreCase) |> equal 0 - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.OrdinalIgnoreCase) |> equal 0 - - testCase "String.IsNullOrEmpty works" <| fun () -> - let args = [("", true); (null, true); ("test", false); (" \t", false)] - for arg in args do - System.String.IsNullOrEmpty(fst arg) - |> equal (snd arg) - - testCase "String.IsNullOrWhiteSpace works" <| fun () -> - let args = [("", true); (null, true); ("test", false); (" \t", true)] - for arg in args do - System.String.IsNullOrWhiteSpace(fst arg) - |> equal (snd arg) - - // System.String - instance methods - - testCase "String.Contains works" <| fun () -> - "ABC".Contains("B") |> equal true - "ABC".Contains("Z") |> equal false - - testCase "String.Split works" <| fun () -> - "a b c d".Split(' ') - |> equal [|"a";"b";"c";"";"d"|] - "a b c d ".Split() - |> equal [|"a";"b";"c";"";"d";""|] - "a-b-c".Split() - |> equal [|"a-b-c"|] - "a-b-c".Split("") - |> equal [|"a-b-c"|] - "a\tb".Split() - |> equal [|"a";"b"|] - "a\nb".Split() - |> equal [|"a";"b"|] - "a\rb".Split() - |> equal [|"a";"b"|] - "a\u2003b".Split() // em space - |> equal [|"a";"b"|] - // "a b c d".Split(null) - // |> equal [|"a";"b";"c";"";"d"|] - // "a\tb".Split(null) - // |> equal [|"a";"b"|] - // "a\u2003b".Split(null) // em space - // |> equal [|"a";"b"|] - let array = "a;b,c".Split(',', ';') - "abc" = array.[0] + array.[1] + array.[2] - |> equal true - "a--b-c".Split([|"--"|], StringSplitOptions.None) - |> equal [|"a";"b-c"|] - " a-- b- c ".Split('-', 2, StringSplitOptions.None) - |> equal [|" a"; "- b- c "|] - "---o---o---".Split("--", StringSplitOptions.None) - |> equal [|""; "-o"; "-o"; "-"|]; - - testCase "String.Split with remove empties works" <| fun () -> - "a b c d ".Split([|" "|], StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"a";"b";"c";"d"|] |> equal true - " a-- b- c ".Split("-", 2, StringSplitOptions.RemoveEmptyEntries) - |> (=) [|" a"; " b- c "|] |> equal true - "---o---o---".Split("--", StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"-o"; "-o"; "-"|] |> equal true - let array = ";,a;b,c".Split([|','; ';'|], StringSplitOptions.RemoveEmptyEntries) - "abc" = array.[0] + array.[1] + array.[2] - |> equal true - - testCase "String.Split with count works" <| fun () -> - let array = "a b c d".Split ([|' '|], 2) - equal "a" array.[0] - equal "b c d" array.[1] - "a;,b,c;d".Split([|','; ';'|], 3, StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"a";"b";"c;d"|] |> equal true - "a-b-c".Split("", System.Int32.MaxValue) - |> (=) [|"a-b-c"|] |> equal true - - testCase "String.Split with empty works" <| fun () -> - let array = "a b cd".Split() - array |> equal [| "a"; "b"; "cd" |] - - testCase "String.Split with trim entries works" <| fun () -> - " a-- b- c ".Split('-', 2, StringSplitOptions.TrimEntries) - |> (=) [|"a"; "- b- c"|] |> equal true - " a-- b- c ".Split('-', 3, StringSplitOptions.TrimEntries) - |> (=) [|"a"; ""; "b- c"|] |> equal true - - testCase "String.Split with trim and remove entries works" <| fun () -> - " a-- b- c ".Split([| "-" |], 2, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [|"a"; "b- c"|] - " a-- b- c ".Split([| '-' |], 3, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [|"a"; "b"; "c"|] - - testCase "String.Split with consecutive separators works" <| fun () -> - " ".Split(" ", 4, StringSplitOptions.None) - |> equal [|"";"";"";" "|] - " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries) - |> equal [||] - " ".Split(" ", 4, StringSplitOptions.TrimEntries) - |> equal [|"";"";"";""|] - " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [||] - - testCase "String.Replace works" <| fun () -> - "abc abc abc".Replace("abc", "d") |> equal "d d d" - // String.Replace does not get stuck in endless loop - "...".Replace(".", "..") |> equal "......" - - testCase "Access char by index works" <| fun () -> - let c = "abcd".[2] - equal 'c' c - equal 'd' (char ((int c) + 1)) - - testCase "String.IndexOf char works" <| fun () -> - "abcd".IndexOf('b') * 100 + "abcd".IndexOf('e') - |> equal 99 - - testCase "String.IndexOf char works with offset" <| fun () -> - "abcdbc".IndexOf('b', 3) - |> equal 4 - - testCase "String.LastIndexOf char works" <| fun () -> - "abcdbc".LastIndexOf('b') * 100 + "abcd".LastIndexOf('e') - |> equal 399 - - testCase "String.LastIndexOf char works with offset" <| fun () -> - "abcdbcebc".LastIndexOf('b', 3) - |> equal 1 - - testCase "String.IndexOf works" <| fun () -> - "abcd".IndexOf("bc") * 100 + "abcd".IndexOf("bd") - |> equal 99 - - testCase "String.IndexOf works with offset" <| fun () -> - "abcdbc".IndexOf("bc", 3) - |> equal 4 - - testCase "String.LastIndexOf works" <| fun () -> - "abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd") - |> equal 399 - - testCase "String.LastIndexOf works with offset" <| fun () -> - "abcdbcebc".LastIndexOf("bc", 3) - |> equal 1 - - testCase "String.IndexOfAny works" <| fun () -> - "abcdbcebc".IndexOfAny([|'b'|]) |> equal 1 - "abcdbcebc".IndexOfAny([|'b'|], 2) |> equal 4 - "abcdbcebc".IndexOfAny([|'b'|], 2, 2) |> equal -1 - "abcdbcebc".IndexOfAny([|'f';'e'|]) |> equal 6 - "abcdbcebc".IndexOfAny([|'f';'e'|], 2) |> equal 6 - "abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1 - // failing - //"abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1 - - testCase "String.StartsWith works" <| fun () -> - let args = [("ab", true); ("cd", false); ("abcdx", false)] - for arg in args do - "abcd".StartsWith(fst arg) - |> equal (snd arg) - - // TODO: StartsWith with StringComparison - // testCase "String.StartsWith with StringComparison works" <| fun () -> - // let args = [("ab", true); ("cd", false); ("abcdx", false)] - // for arg in args do - // "ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase) - // |> equal (snd arg) - - testCase "String.EndsWith works" <| fun () -> - let args = [("ab", false); ("cd", true); ("abcdx", false)] - for arg in args do - "abcd".EndsWith(fst arg) - |> equal (snd arg) - - testCase "String.Trim works" <| fun () -> - " abc ".Trim() - |> equal "abc" - - testCase "String.Trim with chars works" <| fun () -> - @"\\\abc///".Trim('\\','/') - |> equal "abc" - - testCase "String.Trim with special chars works" <| fun () -> - @"()[]{}abc/.?*+-^$|\".Trim(@"()[]{}/.?*+-^$|\".ToCharArray()) - |> equal "abc" - - testCase "String.TrimStart works" <| fun () -> - "!!--abc ".TrimStart('!','-') - |> equal "abc " - - testCase "String.TrimStart with chars works" <| fun () -> - " abc ".TrimStart() - |> equal "abc " - - testCase "String.TrimEnd works" <| fun () -> - " abc ".TrimEnd() - |> equal " abc" - - testCase "String.TrimEnd with chars works" <| fun () -> - " abc??**".TrimEnd('*','?') - |> equal " abc" - @"\foo\bar\".Replace("\\", "/").TrimEnd('/') - |> equal "/foo/bar" - - testCase "String.Empty works" <| fun () -> - let s = String.Empty - s |> equal "" - - testCase "String.Chars works" <| fun () -> - let input = "hello" - input.Chars(2) - |> equal 'l' - - testCase "String.Substring works" <| fun () -> - "abcdefg".Substring(2) - |> equal "cdefg" - - testCase "String.Substring works with length" <| fun () -> - "abcdefg".Substring(2, 2) - |> equal "cd" - - testCase "String.Substring throws error if startIndex or length are out of bounds" <| fun () -> // See #1955 - let throws f = - try f () |> ignore; false - with _ -> true - throws (fun _ -> "abcdefg".Substring(20)) |> equal true - throws (fun _ -> "abcdefg".Substring(2, 10)) |> equal true - - testCase "String.ToUpper works" <| fun () -> - "AbC".ToUpper() |> equal "ABC" - - testCase "String.ToLower works" <| fun () -> - "aBc".ToLower() |> equal "abc" - - testCase "String.ToUpperInvariant works" <| fun () -> - "AbC".ToUpperInvariant() |> equal "ABC" - - testCase "String.ToLowerInvariant works" <| fun () -> - "aBc".ToLowerInvariant() |> equal "abc" - - testCase "String.Length works" <| fun () -> - "AbC".Length |> equal 3 - - testCase "String item works" <| fun () -> - "AbC".[1] |> equal 'b' - - testCase "String.ToCharArray works" <| fun () -> - let arr = "abcd".ToCharArray() - equal "c" (string arr.[2]) - arr |> Array.map (fun _ -> 1) |> Array.sum - |> equal arr.Length - - // TODO: surrogate pairs - // testCase "String enumeration handles surrogates pairs" <| fun () -> // See #1279 - // let unicodeString = ".\U0001f404." - // unicodeString |> List.ofSeq |> Seq.length |> equal 4 - // String.length unicodeString |> equal 4 - // let mutable len = 0 - // for i in unicodeString do - // len <- len + 1 - // equal 4 len - - testCase "String.Join works" <| fun () -> - String.Join("--", "a", "b", "c") - |> equal "a--b--c" - String.Join("--", seq { yield "a"; yield "b"; yield "c" }) - |> equal "a--b--c" - - testCase "String.Join with indices works" <| fun () -> - String.Join("**", [|"a"; "b"; "c"; "d"|], 1, 2) - |> equal "b**c" - String.Join("*", [|"a"; "b"; "c"; "d"|], 1, 3) - |> equal "b*c*d" - - testCase "String.Join works with chars" <| fun () -> // See #1524 - // TODO: It's difficult to make this work because the type - // of ParamArray is obj[] so we need to check each arg one by one. - // String.Join("--", 'a', 'b', 'c') |> equal "a--b--c" - - String.Join("--", seq { yield 'a'; yield 'b'; yield 'c' }) - |> equal "a--b--c" - [0..10] - |> List.map (fun _ -> '*') - |> fun chars -> String.Join("", chars) - |> equal "***********" - - // TODO: BigInts - // testCase "String.Join with big integers works" <| fun () -> - // String.Join("--", [|3I; 5I|]) - // |> equal "3--5" - // String.Join("--", 3I, 5I) - // |> equal "3--5" - - testCase "String.Join with single argument works" <| fun () -> // See #1182 - String.Join(",", "abc") |> equal "abc" - String.Join(",", [|"abc"|]) |> equal "abc" - String.Join(",", ["abc"]) |> equal "abc" - - testCase "System.String.Concat works" <| fun () -> - String.Concat("a", "b", "c") - |> equal "abc" - String.Concat(seq { yield "a"; yield "b"; yield "c" }) - |> equal "abc" - - testCase "System.String.Join with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.Join("", a) - s.Length |> equal n - - testCase "System.String.Join with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.Join("", a) - s.Length |> equal n - - testCase "System.String.Concat with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.Concat(a) - s.Length |> equal n - - testCase "System.String.Concat with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.Concat(a) - s.Length |> equal n - - testCase "String.concat with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.concat "" a - s.Length |> equal n - - testCase "String.concat with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.concat "" a - s.Length |> equal n - - testCase "String.Remove works" <| fun () -> - "abcd".Remove(2) - |> equal "ab" - "abcd".Remove(1,2) - |> equal "ad" - "abcd".Remove(0,2) - |> equal "cd" - "abcd".Remove(0,4) - |> equal "" - "abcd".Remove(0,0) - |> equal "abcd" - - testCase "String.Insert work" <| fun () -> - "foobar".Insert(3, " is ") - |> equal "foo is bar" - - testCase "Enumerating string works" <| fun () -> - let mutable res = "" - for c in "HELLO" |> Seq.rev do - res <- res + (string c) - equal "OLLEH" res - - // String - F# module functions - - testCase "String.concat works" <| fun () -> - String.concat "--" ["a"; "b"; "c"] |> equal "a--b--c" - seq { yield "a"; yield "b"; yield "c" } - |> String.concat "-" |> equal "a-b-c" - - testCase "String.forall and exists work" <| fun () -> - "!!!" |> String.forall (fun c -> c = '!') |> equal true - "a!a" |> String.forall (fun c -> c = '!') |> equal false - "aaa" |> String.forall (fun c -> c = '!') |> equal false - - testCase "String.init works" <| fun () -> - String.init 3 (fun i -> "a") - |> equal "aaa" - - testCase "String.collect works" <| fun () -> - "abc" |> String.collect (fun c -> "bcd") - |> equal "bcdbcdbcd" - - testCase "String.iter works" <| fun () -> - let mutable res = "" - "Hello world!" - |> String.iter (fun c -> res <- res + c.ToString()) - equal "Hello world!" res - - testCase "String.iteri works" <| fun () -> - let mutable res = "" - "Hello world!" - |> String.iteri (fun c i -> res <- res + i.ToString() + c.ToString()) - equal "H0e1l2l3o4 5w6o7r8l9d10!11" res - - testCase "String.length (function) works" <| fun () -> - "AbC" |> String.length - |> equal 3 - - testCase "String.map works" <| fun () -> - "Hello world!" |> String.map (fun c -> if c = 'H' then '_' else c) - |> equal "_ello world!" - - testCase "String.mapi works" <| fun () -> - "Hello world!" |> String.mapi (fun i c -> if i = 1 || c = 'H' then '_' else c) - |> equal "__llo world!" - - testCase "String.replicate works" <| fun () -> - String.replicate 3 "hi there" - |> equal "hi therehi therehi there" - - testCase "String.IsNullOrWhiteSpace works on string with blanks" <| fun () -> - String.IsNullOrWhiteSpace "Fri Jun 30 2017 12:30:00 GMT+0200 (Mitteleuropäische Sommerzeit)" - |> equal false - - testCase "String.IsNullOrWhiteSpace works on blank only string" <| fun () -> - String.IsNullOrWhiteSpace " " - |> equal true - - testCase "String.filter works" <| fun () -> - String.filter (fun x -> x <> '.') "a.b.c" - |> equal "abc" - - testCase "String.filter works when predicate matches everything" <| fun () -> - String.filter (fun x -> x <> '.') "abc" - |> equal "abc" - - testCase "String.filter works when predicate doesn't match" <| fun () -> - String.filter (fun x -> x <> '.') "..." - |> equal "" - - testCase "String.filter works with empty string" <| fun () -> - String.filter (fun x -> x <> '.') "" - |> equal "" - - // TODO: Environment -// #if FABLE_COMPILER -// testCase "System.Environment.NewLine works" <| fun () -> -// System.Environment.NewLine -// |> equal "\n" -// #endif - - // TODO: Uri -// testCase "System.Uri.UnescapeDataString works" <| fun () -> -// System.Uri.UnescapeDataString("Kevin%20van%20Zonneveld%21") |> equal "Kevin van Zonneveld!" -// System.Uri.UnescapeDataString("http%3A%2F%2Fkvz.io%2F") |> equal "http://kvz.io/" -// System.Uri.UnescapeDataString("http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a") -// |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" -// -// testCase "System.Uri.EscapeDataString works" <| fun () -> -// System.Uri.EscapeDataString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld%21" -// System.Uri.EscapeDataString("http://kvz.io/") |> equal "http%3A%2F%2Fkvz.io%2F" -// System.Uri.EscapeDataString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") -// |> equal "http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a" -// -// testCase "System.Uri.EscapeUriString works" <| fun () -> -// System.Uri.EscapeUriString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld!" -// System.Uri.EscapeUriString("http://kvz.io/") |> equal "http://kvz.io/" -// System.Uri.EscapeUriString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") -// |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" - - testCase "interpolated string with double % should be unescaped" <| fun () -> - $"{100}%%" |> equal "100%" - - testCase "interpolated string with double % should not interfere with holes afterwards " <| fun () -> - $"%%{99. - 1.5}".Replace(",", ".") |> equal "%97.5" - - testCase "interpolated string with double braces should be unescaped" <| fun () -> - $"{{ {100} }}" |> equal "{ 100 }" - - // TODO: interpolation with formatting - // testCase "interpolated string with format and double % should be unescaped" <| fun () -> - // $"%.2f{100.4566666}%%" |> equal "100.46%" - - // testCase "interpolated string with format and double braces should be unescaped" <| fun () -> - // $"{{ %.2f{100.4566666} }}" |> equal "{ 100.46 }" - - testCase "interpolated string with consecutive holes work" <| fun () -> - $"""{"foo"}{5}""" |> equal "foo5" - $"""%s{"foo"}%i{5}""" |> equal "foo5" - $"""{"foo"}/{5}.fsi""" |> equal "foo/5.fsi" - - // TODO: FormattableString -// testCase "Can create FormattableString" <| fun () -> -// let orderAmount = 100 -// let convert (s: FormattableString) = s -// let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" -// s.Format |> equal "You owe: {0:N5} {1} {2}" -// s.ArgumentCount |> equal 3 -// s.GetArgument(2) |> equal (box true) -// s.GetArguments() |> equal [|100; 3; true|] -// let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" -// s2.Format |> equal "{0}This is \"{1}\" awesome!" -// s2.GetArguments() |> equal [|box 7; box "really"|] -// let s3: FormattableString = $"""I have no holes""" -// s3.Format |> equal "I have no holes" -// s3.GetArguments() |> equal [||] -// let s4: FormattableString = $"I have `backticks`" -// s4.Format |> equal "I have `backticks`" -// let s5: FormattableString = $"I have {{escaped braces}} and %%percentage%%" -// s5.Format |> equal "I have {{escaped braces}} and %percentage%" + testCase "Backslash is escaped in interpolated strings" <| fun () -> // See #2649 + $"\\" |> equal @"\" + $"\\".Length |> equal 1 + $@"\" |> equal @"\" + $@"\".Length |> equal 1 + @$"\" |> equal @"\" + @$"\".Length |> equal 1 + $"\\{4}" |> equal @"\4" + $"\\{4}".Length |> equal 2 + $@"\{4}" |> equal @"\4" + $@"\{4}".Length |> equal 2 + @$"\{4}" |> equal @"\4" + @$"\{4}".Length |> equal 2 + + // testCase "Extended string interpolation syntax" <| fun () -> + // let classAttr = "item-panel" + // let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""// + // cssNew |> equal ".item-panel:hover {background-color: #eee;}" + + // testCase "sprintf \"%A\" with lists works" <| fun () -> + // let xs = ["Hi"; "Hello"; "Hola"] + // (sprintf "%A" xs).Replace("\"", "") |> equal "[Hi; Hello; Hola]" + + // testCase "sprintf \"%A\" with nested lists works" <| fun () -> + // let xs = [["Hi"]; ["Hello"]; ["Hola"]] + // (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" + + // testCase "sprintf \"%A\" with sequences works" <| fun () -> + // let xs = seq { "Hi"; "Hello"; "Hola" } + // sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true + + // testCase "Storing result of Seq.tail and printing the result several times works. Related to #1996" <| fun () -> + // let tweets = seq { "Hi"; "Hello"; "Hola" } + // let tweetsTailR: seq = tweets |> Seq.tail + + // let a = sprintf "%A" (tweetsTailR) + // let b = sprintf "%A" (tweetsTailR) + + // containsInOrder ["Hello"; "Hola"] a |> equal true + // containsInOrder ["Hello"; "Hola"] b |> equal true + + // testCase "sprintf \"%X\" works" <| fun () -> + // //These should all be the Native JS Versions (except int64 / uint64) + // //See #1530 for more information. + + // sprintf "255: %X" 255 |> equal "255: FF" + // sprintf "255: %x" 255 |> equal "255: ff" + // sprintf "-255: %X" -255 |> equal "-255: FFFFFF01" + // sprintf "4095L: %X" 4095L |> equal "4095L: FFF" + // sprintf "-4095L: %X" -4095L |> equal "-4095L: FFFFFFFFFFFFF001" + // sprintf "1 <<< 31: %x" (1 <<< 31) |> equal "1 <<< 31: 80000000" + // sprintf "1u <<< 31: %x" (1u <<< 31) |> equal "1u <<< 31: 80000000" + // sprintf "2147483649L: %x" 2147483649L |> equal "2147483649L: 80000001" + // sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" + // sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" + // sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" + + // testCase "sprintf integers with sign and padding works" <| fun () -> // See #1931 + // sprintf "%+04i" 1 |> equal "+001" + // sprintf "%+04i" -1 |> equal "-001" + // sprintf "%5d" -5 |> equal " -5" + // sprintf "%5d" -5L |> equal " -5" + // sprintf "%- 4i" 5 |> equal " 5 " + + // testCase "parameterized padding works" <| fun () -> // See #2336 + // sprintf "[%*s][%*s]" 6 "Hello" 5 "Foo" + // |> equal "[ Hello][ Foo]" + + // testCase "String.Format should fail if there are less arguments than placeholders" <| fun () -> // See #2768 + // throwsAnyError <| fun () -> String.Format ("Hello {0}", args = Array.empty) + + // testCase "String.Format combining padding and zeroes pattern works" <| fun () -> + // String.Format(CultureInfo.InvariantCulture, "{0:++0.00++}", -5000.5657) |> equal "-++5000.57++" + // String.Format(CultureInfo.InvariantCulture, "{0:000.00}foo", 5) |> equal "005.00foo" + // String.Format(CultureInfo.InvariantCulture, "{0,-8:000.00}foo", 12.456) |> equal "012.46 foo" + + // testCase "String.Format {0:x} works" <| fun () -> + // //See above comment on expected values + // String.Format(CultureInfo.InvariantCulture, "255: {0:X}", 255) |> equal "255: FF" + // String.Format(CultureInfo.InvariantCulture, "255: {0:x}", 255) |> equal "255: ff" + // String.Format(CultureInfo.InvariantCulture, "-255: {0:X}", -255) |> equal "-255: FFFFFF01" + // String.Format(CultureInfo.InvariantCulture, "4095L: {0:X}", 4095L) |> equal "4095L: FFF" + // String.Format(CultureInfo.InvariantCulture, "-4095L: {0:X}", -4095L) |> equal "-4095L: FFFFFFFFFFFFF001" + // String.Format(CultureInfo.InvariantCulture, "1 <<< 31: {0:x}", (1 <<< 31)) |> equal "1 <<< 31: 80000000" + // String.Format(CultureInfo.InvariantCulture, "1u <<< 31: {0:x}", (1u <<< 31)) |> equal "1u <<< 31: 80000000" + // String.Format(CultureInfo.InvariantCulture, "2147483649L: {0:x}", 2147483649L) |> equal "2147483649L: 80000001" + // String.Format(CultureInfo.InvariantCulture, "2147483650uL: {0:x}", 2147483650uL) |> equal "2147483650uL: 80000002" + // String.Format(CultureInfo.InvariantCulture, "1L <<< 63: {0:x}", (1L <<< 63)) |> equal "1L <<< 63: 8000000000000000" + // String.Format(CultureInfo.InvariantCulture, "1uL <<< 63: {0:x}", (1uL <<< 63)) |> equal "1uL <<< 63: 8000000000000000" + + // testCase "String.Format {0:x} with precision works" <| fun () -> + // String.Format(CultureInfo.InvariantCulture, "#{0:X3}", 0xC149D) |> equal "#C149D" + // String.Format(CultureInfo.InvariantCulture, "#{0:X6}", 0xC149D) |> equal "#0C149D" + + // testCase "String.Format works with thousands separator" <| fun () -> + // String.Format(CultureInfo.InvariantCulture, "{0}", 12343235354.6547757) |> equal "12343235354.654776" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757) |> equal "12,343,235,354.655" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757M) |> equal "12,343,235,354.655" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456) |> equal "123.46" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456M) |> equal "123.46" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123438192123.456M) |> equal "123,438,192,123.46" + // String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 1.456M) |> equal "1" + // String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1.456M) |> equal "01" + + // testCase "String.Format can omit decimal digits" <| fun () -> + // String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 12343235354.6547757) |> equal "12,343,235,355" + // String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.6547757) |> equal "12,343,235,355" + // String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.) |> equal "12,343,235,354" + // String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354.) |> equal "12343235354" + // String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354.) |> equal "12343235354" + + // String.Format(CultureInfo.InvariantCulture, "{0:#,#}", 12343235354.6547757M) |> equal "12,343,235,355" + // String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354.6547757M) |> equal "12,343,235,355" + + // String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1234323535) |> equal "1,234,323,535" + // String.Format(CultureInfo.InvariantCulture, "{0:#}", 1234323535) |> equal "1234323535" + // String.Format(CultureInfo.InvariantCulture, "{0:0}", 1234323535) |> equal "1234323535" + + // String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354M) |> equal "12,343,235,354" + // String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 343235354M) |> equal "343,235,354" + // String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354M) |> equal "12343235354" + // String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354M) |> equal "12343235354" + + // testCase "ToString formatted works with decimals" <| fun () -> // See #2276 + // let decimal = 78.6M + // decimal.ToString("0.000").Replace(",", ".") |> equal "78.600" + + // testCase "Printf works with generic argument" <| fun () -> + // spr "bar %s" "a" |> equal "bar a" + // spr "foo %i %i" 3 5 |> equal "foo 3 5" + // let f1 = spr "foo %i %i" + // let f2 = f1 2 + // f2 2 |> equal "foo 2 2" + // let f1 = spr "foo %i %i %i" + // let f2 = f1 2 + // let f3 = f2 2 + // f3 2 |> equal "foo 2 2 2" + + // testCase "Printf in sequence is not erased" <| fun () -> + // let x = sprintf "Foo" + // let y = sprintf "B%sr" "a" + // x + y |> equal "FooBar" + + // testCase "String.Format works" <| fun () -> + // let arg1, arg2, arg3 = "F#", "Fable", "Babel" + // String.Format(CultureInfo.InvariantCulture, "{2} is to {1} what {1} is to {0}", arg1, arg2, arg3) + // |> equal "Babel is to Fable what Fable is to F#" + + // testCase "String.Format with extra formatting works" <| fun () -> + // let i = 0.5466788 + // let dt = DateTime(2014, 9, 26).AddMinutes(19.) + // String.Format(CultureInfo.InvariantCulture, "{0:F2} {0:P2} {1:yyyy-MM-dd HH:mm}", i, dt) + // .Replace(",", ".").Replace(" %", "%") + // |> equal "0.55 54.67% 2014-09-26 00:19" + + // testCase "Padding works" <| fun () -> + // "3.14".PadLeft(10) |> equal " 3.14" + // "3.14".PadRight(10) |> equal "3.14 " + // "22".PadLeft(10, '0') |> equal "0000000022" + // "-22".PadRight(10, 'X') |> equal "-22XXXXXXX" + // "333".PadLeft(1) |> equal "333" + + // testCase "Padding with sprintf works" <| fun () -> + // sprintf "%10.1f" 3.14 |> equal " 3.1" + // sprintf "%-10.1f" 3.14 |> equal "3.1 " + // sprintf "%+010i" 22 |> equal "+000000022" + // sprintf "%+0-10i" -22 |> equal "-22 " + + // testCase "Padding with String.Format works" <| fun () -> + // String.Format(CultureInfo.InvariantCulture, "{0,10:F1}", 3.14) |> equal " 3.1" + // String.Format(CultureInfo.InvariantCulture, "{0,-10:F1}", 3.14) |> equal "3.1 " + // String.Format(CultureInfo.InvariantCulture, "{0,10}", 22) |> equal " 22" + // String.Format(CultureInfo.InvariantCulture, "{0,-10}", -22) |> equal "-22 " + + // Conversions + + testCase "Conversion char to int works" <| fun () -> + equal 97 (int 'a') + equal 'a' (char 97) + + testCase "Conversion string to char works" <| fun () -> + equal 'a' (char "a") + equal "a" (string 'a') + + testCase "Conversion string to negative int8 works" <| fun () -> + equal -5y (int8 "-5") + equal "-5" (string -5y) + + testCase "Conversion string to negative int16 works" <| fun () -> + equal -5s (int16 "-5") + equal "-5" (string -5s) + + testCase "Conversion string to negative int32 works" <| fun () -> + equal -5 (int32 "-5") + equal "-5" (string -5) + + testCase "Conversion string to negative int64 works" <| fun () -> + equal -5L (int64 "-5") + equal "-5" (string -5L) + + testCase "Conversion string to int8 works" <| fun () -> + equal 5y (int8 "5") + equal "5" (string 5y) + + testCase "Conversion string to int16 works" <| fun () -> + equal 5s (int16 "5") + equal "5" (string 5s) + + testCase "Conversion string to int32 works" <| fun () -> + equal 5 (int32 "5") + equal "5" (string 5) + + testCase "Conversion string to int64 works" <| fun () -> + equal 5L (int64 "5") + equal "5" (string 5L) + + testCase "Conversion string to uint8 works" <| fun () -> + equal 5uy (uint8 "5") + equal "5" (string 5uy) + + testCase "Conversion string to uint16 works" <| fun () -> + equal 5us (uint16 "5") + equal "5" (string 5us) + + testCase "Conversion string to uint32 works" <| fun () -> + equal 5u (uint32 "5") + equal "5" (string 5u) + + testCase "Conversion string to uint64 works" <| fun () -> + equal 5uL (uint64 "5") + equal "5" (string 5uL) + + testCase "Conversion string to single works" <| fun () -> + equal 5.f (float32 "5.0") + equal -5.f (float32 "-5.0") + (string 5.f).StartsWith("5") |> equal true + equal 5.25f (float32 "5.25") + (string 5.25f).StartsWith("5.25") |> equal true + + testCase "Conversion string to double works" <| fun () -> + equal 5. (float "5.0") + equal -5. (float "-5.0") + (string 5.).StartsWith("5") |> equal true + equal 5.25 (float "5.25") + (string 5.25).StartsWith("5.25") |> equal true + + // testCase "Conversion string to decimal works" <| fun () -> + // equal 5.m (decimal "5.0") + // equal -5.m (decimal "-5.0") + // (string 5.m).StartsWith("5") |> equal true + // equal 5.25m (decimal "5.25") + // (string 5.25m).StartsWith("5.25") |> equal true + + // System.String - constructors + + testCase "String.ctor(char[]) works" <| fun () -> + System.String([|'f'; 'a'; 'b'; 'l'; 'e'|]) + |> equal "fable" + + testCase "String.ctor(char, int) works" <| fun () -> + System.String('f', 5) + |> equal "fffff" + + testCase "String.ctor(char[], int, int) works" <| fun () -> + System.String([|'f'; 'a'; 'b'; 'l'; 'e'|], 1, 3) + |> equal "abl" + + // System.String - static methods + + testCase "System.String.Equals works" <| fun () -> + System.String.Equals("abc", "abc") |> equal true + System.String.Equals("ABC", "abc") |> equal false + System.String.Equals("abc", "abd") |> equal false + "abc".Equals("abc") |> equal true + "ABC".Equals("abc") |> equal false + "abc".Equals("abd") |> equal false + System.String.Equals("ABC", "abc", StringComparison.Ordinal) |> equal false + System.String.Equals("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal true + "ABC".Equals("abc", StringComparison.Ordinal) |> equal false + "ABC".Equals("abc", StringComparison.OrdinalIgnoreCase) |> equal true + + // TODO: Dart seems to compare lower/uppercase strings differently as how .NET does + testCase "String.Compare works" <| fun () -> + // "ABC".CompareTo("abc") > 0 |> equal true + // System.String.Compare("abc", "abc") |> equal 0 + // System.String.Compare("ABC", "abc") |> equal 1 + // System.String.Compare("abc", "abd") |> equal -1 + // System.String.Compare("bbc", "abd") |> equal 1 + // System.String.Compare("ABC", "abc", false) |> equal 1 + System.String.Compare("ABC", "abc", true) |> equal 0 + System.String.Compare("ABC", "abd", true) |> equal -1 + System.String.Compare("BBC", "abd", true) |> equal 1 + // System.String.Compare("ABC", "abc", StringComparison.CurrentCulture) > 0 |> equal true + // System.String.Compare("ABC", "abc", StringComparison.Ordinal) < 0 |> equal true + System.String.Compare("ABC", "abc", StringComparison.CurrentCultureIgnoreCase) |> equal 0 + System.String.Compare("ABC", "abc", StringComparison.InvariantCultureIgnoreCase) |> equal 0 + System.String.Compare("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal 0 + // System.String.Compare("abc", 0, "b", 0, 3) |> equal -1 + // System.String.Compare("abc", 1, "bcd", 0, 2) |> equal 0 + // System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCulture) > 0 |> equal true + // System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.Ordinal) < 0 |> equal true + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCultureIgnoreCase) |> equal 0 + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.InvariantCultureIgnoreCase) |> equal 0 + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.OrdinalIgnoreCase) |> equal 0 + + testCase "String.IsNullOrEmpty works" <| fun () -> + let args = [("", true); (null, true); ("test", false); (" \t", false)] + for arg in args do + System.String.IsNullOrEmpty(fst arg) + |> equal (snd arg) + + testCase "String.IsNullOrWhiteSpace works" <| fun () -> + let args = [("", true); (null, true); ("test", false); (" \t", true)] + for arg in args do + System.String.IsNullOrWhiteSpace(fst arg) + |> equal (snd arg) + + // System.String - instance methods + + testCase "String.Contains works" <| fun () -> + "ABC".Contains("B") |> equal true + "ABC".Contains("Z") |> equal false + + testCase "String.Split works" <| fun () -> + "a b c d".Split(' ') + |> equal [|"a";"b";"c";"";"d"|] + "a b c d ".Split() + |> equal [|"a";"b";"c";"";"d";""|] + "a-b-c".Split() + |> equal [|"a-b-c"|] + "a-b-c".Split("") + |> equal [|"a-b-c"|] + "a\tb".Split() + |> equal [|"a";"b"|] + "a\nb".Split() + |> equal [|"a";"b"|] + "a\rb".Split() + |> equal [|"a";"b"|] + "a\u2003b".Split() // em space + |> equal [|"a";"b"|] + // "a b c d".Split(null) + // |> equal [|"a";"b";"c";"";"d"|] + // "a\tb".Split(null) + // |> equal [|"a";"b"|] + // "a\u2003b".Split(null) // em space + // |> equal [|"a";"b"|] + let array = "a;b,c".Split(',', ';') + "abc" = array.[0] + array.[1] + array.[2] + |> equal true + "a--b-c".Split([|"--"|], StringSplitOptions.None) + |> equal [|"a";"b-c"|] + " a-- b- c ".Split('-', 2, StringSplitOptions.None) + |> equal [|" a"; "- b- c "|] + "---o---o---".Split("--", StringSplitOptions.None) + |> equal [|""; "-o"; "-o"; "-"|]; + + testCase "String.Split with remove empties works" <| fun () -> + "a b c d ".Split([|" "|], StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"a";"b";"c";"d"|] |> equal true + " a-- b- c ".Split("-", 2, StringSplitOptions.RemoveEmptyEntries) + |> (=) [|" a"; " b- c "|] |> equal true + "---o---o---".Split("--", StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"-o"; "-o"; "-"|] |> equal true + let array = ";,a;b,c".Split([|','; ';'|], StringSplitOptions.RemoveEmptyEntries) + "abc" = array.[0] + array.[1] + array.[2] + |> equal true + + testCase "String.Split with count works" <| fun () -> + let array = "a b c d".Split ([|' '|], 2) + equal "a" array.[0] + equal "b c d" array.[1] + "a;,b,c;d".Split([|','; ';'|], 3, StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"a";"b";"c;d"|] |> equal true + "a-b-c".Split("", System.Int32.MaxValue) + |> (=) [|"a-b-c"|] |> equal true + + testCase "String.Split with empty works" <| fun () -> + let array = "a b cd".Split() + array |> equal [| "a"; "b"; "cd" |] + + testCase "String.Split with trim entries works" <| fun () -> + " a-- b- c ".Split('-', 2, StringSplitOptions.TrimEntries) + |> (=) [|"a"; "- b- c"|] |> equal true + " a-- b- c ".Split('-', 3, StringSplitOptions.TrimEntries) + |> (=) [|"a"; ""; "b- c"|] |> equal true + + testCase "String.Split with trim and remove entries works" <| fun () -> + " a-- b- c ".Split([| "-" |], 2, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [|"a"; "b- c"|] + " a-- b- c ".Split([| '-' |], 3, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [|"a"; "b"; "c"|] + + testCase "String.Split with consecutive separators works" <| fun () -> + " ".Split(" ", 4, StringSplitOptions.None) + |> equal [|"";"";"";" "|] + " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries) + |> equal [||] + " ".Split(" ", 4, StringSplitOptions.TrimEntries) + |> equal [|"";"";"";""|] + " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [||] + + testCase "String.Replace works" <| fun () -> + "abc abc abc".Replace("abc", "d") |> equal "d d d" + // String.Replace does not get stuck in endless loop + "...".Replace(".", "..") |> equal "......" + + testCase "Access char by index works" <| fun () -> + let c = "abcd".[2] + equal 'c' c + equal 'd' (char ((int c) + 1)) + + testCase "String.IndexOf char works" <| fun () -> + "abcd".IndexOf('b') * 100 + "abcd".IndexOf('e') + |> equal 99 + + testCase "String.IndexOf char works with offset" <| fun () -> + "abcdbc".IndexOf('b', 3) + |> equal 4 + + testCase "String.LastIndexOf char works" <| fun () -> + "abcdbc".LastIndexOf('b') * 100 + "abcd".LastIndexOf('e') + |> equal 399 + + testCase "String.LastIndexOf char works with offset" <| fun () -> + "abcdbcebc".LastIndexOf('b', 3) + |> equal 1 + + testCase "String.IndexOf works" <| fun () -> + "abcd".IndexOf("bc") * 100 + "abcd".IndexOf("bd") + |> equal 99 + + testCase "String.IndexOf works with offset" <| fun () -> + "abcdbc".IndexOf("bc", 3) + |> equal 4 + + testCase "String.LastIndexOf works" <| fun () -> + "abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd") + |> equal 399 + + testCase "String.LastIndexOf works with offset" <| fun () -> + "abcdbcebc".LastIndexOf("bc", 3) + |> equal 1 + + testCase "String.IndexOfAny works" <| fun () -> + "abcdbcebc".IndexOfAny([|'b'|]) |> equal 1 + "abcdbcebc".IndexOfAny([|'b'|], 2) |> equal 4 + "abcdbcebc".IndexOfAny([|'b'|], 2, 2) |> equal -1 + "abcdbcebc".IndexOfAny([|'f';'e'|]) |> equal 6 + "abcdbcebc".IndexOfAny([|'f';'e'|], 2) |> equal 6 + "abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1 + // failing + //"abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1 + + testCase "String.StartsWith works" <| fun () -> + let args = [("ab", true); ("cd", false); ("abcdx", false)] + for arg in args do + "abcd".StartsWith(fst arg) + |> equal (snd arg) + + // // TODO: StartsWith with StringComparison + // testCase "String.StartsWith with StringComparison works" <| fun () -> + // let args = [("ab", true); ("cd", false); ("abcdx", false)] + // for arg in args do + // "ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase) + // |> equal (snd arg) + + testCase "String.EndsWith works" <| fun () -> + let args = [("ab", false); ("cd", true); ("abcdx", false)] + for arg in args do + "abcd".EndsWith(fst arg) + |> equal (snd arg) + + testCase "String.Trim works" <| fun () -> + " abc ".Trim() + |> equal "abc" + + testCase "String.Trim with chars works" <| fun () -> + @"\\\abc///".Trim('\\','/') + |> equal "abc" + + testCase "String.Trim with special chars works" <| fun () -> + @"()[]{}abc/.?*+-^$|\".Trim(@"()[]{}/.?*+-^$|\".ToCharArray()) + |> equal "abc" + + testCase "String.TrimStart works" <| fun () -> + "!!--abc ".TrimStart('!','-') + |> equal "abc " + + testCase "String.TrimStart with chars works" <| fun () -> + " abc ".TrimStart() + |> equal "abc " + + testCase "String.TrimEnd works" <| fun () -> + " abc ".TrimEnd() + |> equal " abc" + + testCase "String.TrimEnd with chars works" <| fun () -> + " abc??**".TrimEnd('*','?') + |> equal " abc" + @"\foo\bar\".Replace("\\", "/").TrimEnd('/') + |> equal "/foo/bar" + + testCase "String.Empty works" <| fun () -> + let s = String.Empty + s |> equal "" + + testCase "String.Chars works" <| fun () -> + let input = "hello" + input.Chars(2) + |> equal 'l' + + testCase "String.Substring works" <| fun () -> + "abcdefg".Substring(2) + |> equal "cdefg" + + testCase "String.Substring works with length" <| fun () -> + "abcdefg".Substring(2, 2) + |> equal "cd" + + testCase "String.Substring throws error if startIndex or length are out of bounds" <| fun () -> // See #1955 + let throws f = + try f () |> ignore; false + with _ -> true + throws (fun _ -> "abcdefg".Substring(20)) |> equal true + throws (fun _ -> "abcdefg".Substring(2, 10)) |> equal true + + testCase "String.ToUpper works" <| fun () -> + "AbC".ToUpper() |> equal "ABC" + + testCase "String.ToLower works" <| fun () -> + "aBc".ToLower() |> equal "abc" + + testCase "String.ToUpperInvariant works" <| fun () -> + "AbC".ToUpperInvariant() |> equal "ABC" + + testCase "String.ToLowerInvariant works" <| fun () -> + "aBc".ToLowerInvariant() |> equal "abc" + + testCase "String.Length works" <| fun () -> + "AbC".Length |> equal 3 + + testCase "String item works" <| fun () -> + "AbC".[1] |> equal 'b' + + testCase "String.ToCharArray works" <| fun () -> + let arr = "abcd".ToCharArray() + equal "c" (string arr.[2]) + arr |> Array.map (fun _ -> 1) |> Array.sum + |> equal arr.Length + + // // TODO: surrogate pairs + // testCase "String enumeration handles surrogates pairs" <| fun () -> // See #1279 + // let unicodeString = ".\U0001f404." + // unicodeString |> List.ofSeq |> Seq.length |> equal 4 + // String.length unicodeString |> equal 4 + // let mutable len = 0 + // for i in unicodeString do + // len <- len + 1 + // equal 4 len + + testCase "String.Join works" <| fun () -> + String.Join("--", "a", "b", "c") + |> equal "a--b--c" + String.Join("--", seq { yield "a"; yield "b"; yield "c" }) + |> equal "a--b--c" + + testCase "String.Join with indices works" <| fun () -> + String.Join("**", [|"a"; "b"; "c"; "d"|], 1, 2) + |> equal "b**c" + String.Join("*", [|"a"; "b"; "c"; "d"|], 1, 3) + |> equal "b*c*d" + + testCase "String.Join works with chars" <| fun () -> // See #1524 + // TODO: It's difficult to make this work because the type + // of ParamArray is obj[] so we need to check each arg one by one. + // String.Join("--", 'a', 'b', 'c') |> equal "a--b--c" + + String.Join("--", seq { yield 'a'; yield 'b'; yield 'c' }) + |> equal "a--b--c" + [0..10] + |> List.map (fun _ -> '*') + |> fun chars -> String.Join("", chars) + |> equal "***********" + + // // TODO: BigInts + // testCase "String.Join with big integers works" <| fun () -> + // String.Join("--", [|3I; 5I|]) + // |> equal "3--5" + // String.Join("--", 3I, 5I) + // |> equal "3--5" + + testCase "String.Join with single argument works" <| fun () -> // See #1182 + String.Join(",", "abc") |> equal "abc" + String.Join(",", [|"abc"|]) |> equal "abc" + String.Join(",", ["abc"]) |> equal "abc" + + testCase "System.String.Concat works" <| fun () -> + String.Concat("a", "b", "c") + |> equal "abc" + String.Concat(seq { yield "a"; yield "b"; yield "c" }) + |> equal "abc" + + testCase "System.String.Join with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.Join("", a) + s.Length |> equal n + + testCase "System.String.Join with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.Join("", a) + s.Length |> equal n + + testCase "System.String.Concat with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.Concat(a) + s.Length |> equal n + + testCase "System.String.Concat with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.Concat(a) + s.Length |> equal n + + testCase "String.concat with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.concat "" a + s.Length |> equal n + + testCase "String.concat with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.concat "" a + s.Length |> equal n + + testCase "String.Remove works" <| fun () -> + "abcd".Remove(2) + |> equal "ab" + "abcd".Remove(1,2) + |> equal "ad" + "abcd".Remove(0,2) + |> equal "cd" + "abcd".Remove(0,4) + |> equal "" + "abcd".Remove(0,0) + |> equal "abcd" + + testCase "String.Insert work" <| fun () -> + "foobar".Insert(3, " is ") + |> equal "foo is bar" + + testCase "Enumerating string works" <| fun () -> + let mutable res = "" + for c in "HELLO" |> Seq.rev do + res <- res + (string c) + equal "OLLEH" res + + // String - F# module functions + + testCase "String.concat works" <| fun () -> + String.concat "--" ["a"; "b"; "c"] |> equal "a--b--c" + seq { yield "a"; yield "b"; yield "c" } + |> String.concat "-" |> equal "a-b-c" + + testCase "String.forall and exists work" <| fun () -> + "!!!" |> String.forall (fun c -> c = '!') |> equal true + "a!a" |> String.forall (fun c -> c = '!') |> equal false + "aaa" |> String.forall (fun c -> c = '!') |> equal false + + testCase "String.init works" <| fun () -> + String.init 3 (fun i -> "a") + |> equal "aaa" + + testCase "String.collect works" <| fun () -> + "abc" |> String.collect (fun c -> "bcd") + |> equal "bcdbcdbcd" + + testCase "String.iter works" <| fun () -> + let mutable res = "" + "Hello world!" + |> String.iter (fun c -> res <- res + c.ToString()) + equal "Hello world!" res + + testCase "String.iteri works" <| fun () -> + let mutable res = "" + "Hello world!" + |> String.iteri (fun c i -> res <- res + i.ToString() + c.ToString()) + equal "H0e1l2l3o4 5w6o7r8l9d10!11" res + + testCase "String.length (function) works" <| fun () -> + "AbC" |> String.length + |> equal 3 + + testCase "String.map works" <| fun () -> + "Hello world!" |> String.map (fun c -> if c = 'H' then '_' else c) + |> equal "_ello world!" + + testCase "String.mapi works" <| fun () -> + "Hello world!" |> String.mapi (fun i c -> if i = 1 || c = 'H' then '_' else c) + |> equal "__llo world!" + + testCase "String.replicate works" <| fun () -> + String.replicate 3 "hi there" + |> equal "hi therehi therehi there" + + testCase "String.IsNullOrWhiteSpace works on string with blanks" <| fun () -> + String.IsNullOrWhiteSpace "Fri Jun 30 2017 12:30:00 GMT+0200 (Mitteleuropäische Sommerzeit)" + |> equal false + + testCase "String.IsNullOrWhiteSpace works on blank only string" <| fun () -> + String.IsNullOrWhiteSpace " " + |> equal true + + testCase "String.filter works" <| fun () -> + String.filter (fun x -> x <> '.') "a.b.c" + |> equal "abc" + + testCase "String.filter works when predicate matches everything" <| fun () -> + String.filter (fun x -> x <> '.') "abc" + |> equal "abc" + + testCase "String.filter works when predicate doesn't match" <| fun () -> + String.filter (fun x -> x <> '.') "..." + |> equal "" + + testCase "String.filter works with empty string" <| fun () -> + String.filter (fun x -> x <> '.') "" + |> equal "" + + // // TODO: Environment + // #if FABLE_COMPILER + // testCase "System.Environment.NewLine works" <| fun () -> + // System.Environment.NewLine + // |> equal "\n" + // #endif + + // // TODO: Uri + // testCase "System.Uri.UnescapeDataString works" <| fun () -> + // System.Uri.UnescapeDataString("Kevin%20van%20Zonneveld%21") |> equal "Kevin van Zonneveld!" + // System.Uri.UnescapeDataString("http%3A%2F%2Fkvz.io%2F") |> equal "http://kvz.io/" + // System.Uri.UnescapeDataString("http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a") + // |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" + + // testCase "System.Uri.EscapeDataString works" <| fun () -> + // System.Uri.EscapeDataString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld%21" + // System.Uri.EscapeDataString("http://kvz.io/") |> equal "http%3A%2F%2Fkvz.io%2F" + // System.Uri.EscapeDataString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") + // |> equal "http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a" + + // testCase "System.Uri.EscapeUriString works" <| fun () -> + // System.Uri.EscapeUriString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld!" + // System.Uri.EscapeUriString("http://kvz.io/") |> equal "http://kvz.io/" + // System.Uri.EscapeUriString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") + // |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" + + // testCase "sprintf with double % should be unescaped" <| fun () -> + // sprintf "%d%%" 100 |> equal "100%" + + // testCase "interpolated string with double % should be unescaped" <| fun () -> + // $"{100}%%" |> equal "100%" + + // testCase "interpolated string with double % should not interfere with holes afterwards " <| fun () -> + // $"%%{99. - 1.5}".Replace(",", ".") |> equal "%97.5" + + // testCase "interpolated string with double braces should be unescaped" <| fun () -> + // $"{{ {100} }}" |> equal "{ 100 }" + + // TODO: interpolation with formatting + // testCase "interpolated string with format and double % should be unescaped" <| fun () -> + // $"%.2f{100.4566666}%%" |> equal "100.46%" + + // testCase "interpolated string with format and double braces should be unescaped" <| fun () -> + // $"{{ %.2f{100.4566666} }}" |> equal "{ 100.46 }" + + testCase "interpolated string with consecutive holes work" <| fun () -> + $"""{"foo"}{5}""" |> equal "foo5" + $"""%s{"foo"}%i{5}""" |> equal "foo5" + $"""{"foo"}/{5}.fsi""" |> equal "foo/5.fsi" + + // // TODO: FormattableString + // testCase "Can create FormattableString" <| fun () -> + // let orderAmount = 100 + // let convert (s: FormattableString) = s + // let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" + // s.Format |> equal "You owe: {0:N5} {1} {2}" + // s.ArgumentCount |> equal 3 + // s.GetArgument(2) |> equal (box true) + // s.GetArguments() |> equal [|100; 3; true|] + // let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" + // s2.Format |> equal "{0}This is \"{1}\" awesome!" + // s2.GetArguments() |> equal [|box 7; box "really"|] + // let s3: FormattableString = $"""I have no holes""" + // s3.Format |> equal "I have no holes" + // s3.GetArguments() |> equal [||] + // let s4: FormattableString = $"I have `backticks`" + // s4.Format |> equal "I have `backticks`" + // let s5: FormattableString = $"I have {{escaped braces}} and %%percentage%%" + // s5.Format |> equal "I have {{escaped braces}} and %percentage%" diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 1feba4f778..1ce1a4156a 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -51,249 +51,263 @@ let containsInOrder (substrings: string list) (str: string) = lastIndex <- i success) -let tests = - testList "Strings" [ - testCase "String literal addition is optimized" <| fun () -> - "bar" + aLiteral |> equal "barfoo" - "bar" + notALiteral |> equal "barfoo" - - testCase "String chunkBySize works" <| fun () -> // See #1296 - "fffff" |> Seq.chunkBySize 3 |> Seq.map String |> Seq.toList - |> equal ["fff"; "ff"] - - // Format - - testCase "StringBuilder works" <| fun () -> - let sb = System.Text.StringBuilder() - sb.Append "Hello" |> ignore - sb.AppendLine () |> ignore - sb.AppendLine "World!" |> ignore - let expected = System.Text.StringBuilder() - .AppendFormat("Hello{0}World!{0}", Environment.NewLine) - .ToString() - sb.ToString() |> equal expected - - testCase "StringBuilder.Lengh works" <| fun () -> - let sb = System.Text.StringBuilder() - sb.Append("Hello") |> ignore - // We don't test the AppendLine for Length because depending on the OS - // the result is different. Unix \n VS Windows \r\n - // sb.AppendLine() |> ignore - equal 5 sb.Length - - testCase "StringBuilder.ToString works with index and length" <| fun () -> - let sb = System.Text.StringBuilder() - sb.Append("Hello") |> ignore - sb.AppendLine() |> ignore - equal "ll" (sb.ToString(2, 2)) - - testCase "StringBuilder.Clear works" <| fun () -> - let builder = new System.Text.StringBuilder() - builder.Append("1111") |> ignore - builder.Clear() |> ignore - equal "" (builder.ToString()) - - testCase "StringBuilder.Append works with various overloads" <| fun () -> - let builder = Text.StringBuilder() - .Append(Text.StringBuilder "aaa") - .Append("bcd".ToCharArray()) - .Append('/') - .Append(true) - .Append(5.2) - .Append(34) - equal "aaabcd/true5.234" (builder.ToString().Replace(",", ".").ToLower()) - - testCase "StringBuilder.Chars works" <| fun () -> - let builder = Text.StringBuilder() - .Append("abc") - equal 'b' (builder.Chars(1)) - - testCase "StringBuilder.Chars throws when index is out of bounds" <| fun () -> - throwsAnyError <| fun () -> - let builder = Text.StringBuilder() - .Append("abc") - builder.Chars(-1) |> ignore - - builder.Chars(3) |> ignore - - testCase "StringBuilder.Replace works" <| fun () -> - let builder = Text.StringBuilder() - .Append("abc") - .Append("abc") - .Replace('a', 'x') - .Replace("bc", "yz") - equal "xyzxyz" (builder.ToString()) - - testCase "StringBuilder index accessor works" <| fun () -> - let builder = Text.StringBuilder() - .Append("abc") - equal 'b' (builder[1]) - - testCase "StringBuilder index setter works" <| fun () -> - let builder = Text.StringBuilder() - .Append("abc") - builder[1] <- 'x' - equal "axc" (builder.ToString()) - - testCase "kprintf works" <| fun () -> - let f (s:string) = s + "XX" - Printf.kprintf f "hello" |> equal "helloXX" - Printf.kprintf f "%X" 255 |> equal "FFXX" - Printf.kprintf f "%.2f %g" 0.5468989 5. |> equal "0.55 5XX" - - testCase "kprintf works indirectly" <| fun () -> // See #1204 - let lines = ResizeArray() - let linef fmt = Printf.ksprintf lines.Add fmt // broken - linef "open %s" "Foo" - lines |> Seq.toList |> equal ["open Foo"] - testCase "kbprintf works" <| fun () -> +let tests = testList "Strings" [ + + testCase "F# nameof works" <| fun () -> + M.f 12 |> equal "x" + nameof M |> equal "M" + nameof M.f |> equal "f" + + testCase "String literal addition is optimized" <| fun () -> + "bar" + aLiteral |> equal "barfoo" + "bar" + notALiteral |> equal "barfoo" + + testCase "String chunkBySize works" <| fun () -> // See #1296 + "fffff" |> Seq.chunkBySize 3 |> Seq.map String |> Seq.toList + |> equal ["fff"; "ff"] + + // StringBuilder + + testCase "StringBuilder works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append "Hello" |> ignore + sb.AppendLine () |> ignore + sb.AppendLine "World!" |> ignore + let expected = System.Text.StringBuilder() + .AppendFormat("Hello{0}World!{0}", Environment.NewLine) + .ToString() + sb.ToString() |> equal expected + + testCase "StringBuilder.Length works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + // We don't test the AppendLine for Length because depending on the OS + // the result is different. Unix \n VS Windows \r\n + // sb.AppendLine() |> ignore + sb.Length |> equal 5 + + testCase "StringBuilder.ToString works with index and length" <| fun () -> + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + sb.AppendLine() |> ignore + sb.ToString(2, 2) |> equal "ll" + + testCase "StringBuilder.Clear works" <| fun () -> + let sb = new System.Text.StringBuilder() + sb.Append("1111") |> ignore + sb.Clear() |> ignore + sb.ToString() |> equal "" + + testCase "StringBuilder.Append works with various overloads" <| fun () -> + let sb = System.Text.StringBuilder() + .Append(System.Text.StringBuilder "aaa") + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .Append(5.2) + .Append(34) + let actual = sb.ToString().Replace(",", ".").ToLower() + actual |> equal "aaabcd/true5.234" + + testCase "StringBuilder.AppendFormat works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.AppendFormat("Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + + testCase "StringBuilder.AppendFormat with provider works" <| fun () -> + let sb = System.Text.StringBuilder() + sb.AppendFormat(CultureInfo.InvariantCulture, "Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + + testCase "StringBuilder.Chars works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(1) |> equal 'b' + + testCase "StringBuilder.Chars throws when index is out of bounds" <| fun () -> + throwsAnyError <| fun () -> let sb = System.Text.StringBuilder() - let mutable i = 0 - let f () = i <- i + 1 - Printf.kbprintf f sb "Hello" - Printf.kbprintf f sb " %s!" "world" - i |> equal 2 - sb.ToString() |> equal "Hello world!" - - testCase "ksprintf curries correctly" <| fun () -> - let append (a: string) b = a + b - - let step1 = Printf.ksprintf append "%d" - let step2 = step1 42 - let result = step2 "The answer is: " - result |> equal "42The answer is: " - - testCase "bprintf works" <| fun () -> - let sb = System.Text.StringBuilder(10) - Printf.bprintf sb "Hello" - Printf.bprintf sb " %s!" "world" - sb.ToString() |> equal "Hello world!" - - testCase "sprintf works" <| fun () -> - // Immediately applied - sprintf "%.2f %g" 0.5468989 5. - |> equal "0.55 5" - // Curried - let printer = sprintf "Hi %s, good %s!" - let printer = printer "Alfonso" - printer "morning" |> equal "Hi Alfonso, good morning!" - printer "evening" |> equal "Hi Alfonso, good evening!" - - testCase "sprintf works II" <| fun () -> - let printer2 = sprintf "Hi %s, good %s%s" "Maxime" - let printer2 = printer2 "afternoon" - printer2 "?" |> equal "Hi Maxime, good afternoon?" - - testCase "sprintf with different decimal digits works" <| fun () -> // See #1932 - sprintf "Percent: %.0f%%" 5.0 |> equal "Percent: 5%" - sprintf "Percent: %.2f%%" 5. |> equal "Percent: 5.00%" - sprintf "Percent: %.1f%%" 5.24 |> equal "Percent: 5.2%" - sprintf "Percent: %.2f%%" 5.268 |> equal "Percent: 5.27%" - sprintf "Percent: %f%%" 5.67 |> equal "Percent: 5.670000%" - - testCase "sprintf displays sign correctly" <| fun () -> // See #1937 - sprintf "%i" 1 |> equal "1" - sprintf "%d" 1 |> equal "1" - sprintf "%d" 1L |> equal "1" - sprintf "%.2f" 1. |> equal "1.00" - sprintf "%i" -1 |> equal "-1" - sprintf "%d" -1 |> equal "-1" - sprintf "%d" -1L |> equal "-1" - sprintf "%.2f" -1. |> equal "-1.00" - - testCase "format string can use and compose string literals" <| fun () -> - let renderedCoordinates = sprintf formatCoordinateBody 0.25 0.75 - let renderedText = sprintf fullFormat 0.25 0.75 - - equal "(0.250000,0.750000)" renderedCoordinates - equal "Person at coordinates(0.250000,0.750000)" renderedText - - testCase "Print.sprintf works" <| fun () -> // See #1216 - let res = Printf.sprintf "%s" "abc" - equal "res: abc" ("res: " + res) - - testCase "sprintf without arguments works" <| fun () -> - sprintf "hello" |> equal "hello" - - testCase "input of print format can be retrieved" <| fun () -> - let pathScan (pf:PrintfFormat<_,_,_,_,'t>) = - let formatStr = pf.Value - formatStr - - equal "/hello/%s" (pathScan "/hello/%s") - - testCase "sprintf with escaped percent symbols works" <| fun () -> // See #195 - let r, r1, r2 = "Ratio", 0.213849, 0.799898 - sprintf "%s1: %.2f%% %s2: %.2f%%" r (r1*100.) r (r2*100.) - |> equal "Ratio1: 21.38% Ratio2: 79.99%" - - testCase "sprintf with percent symbols in arguments works" <| fun () -> // See #329 - let same s = sprintf "%s" s |> equal s - same "%" - same "%%" - same "%%%" - same "%%%%" - same "% %" - same "%% %%" - same "%% % % %%" - - testCase "Fix #2398: Exception when two successive string format placeholders and value of first one ends in `%`" <| fun () -> - sprintf "%c%s" '%' "text" |> equal "%text" - - testCase "Unions with sprintf %A" <| fun () -> - Bar(1,5) |> sprintf "%A" |> equal "Bar (1, 5)" - Foo1 4.5 |> sprintf "%A" |> equal "Foo1 4.5" - Foo4 Foo3 |> sprintf "%A" |> equal "Foo4 Foo3" - Foo4(Foo1 4.5) |> sprintf "%A" |> equal "Foo4 (Foo1 4.5)" - Foo3 |> sprintf "%A" |> equal "Foo3" - - testCase "Unions with string operator" <| fun () -> - Bar(1,5) |> string |> equal "Bar (1, 5)" - Foo1 4.5 |> string |> equal "Foo1 4.5" - Foo4 Foo3 |> string |> equal "Foo4 Foo3" - Foo4(Foo1 4.5) |>string |> equal "Foo4 (Foo1 4.5)" - Foo3 |> string |> equal "Foo3" - - testCase "sprintf \"%O\" with overloaded string works" <| fun () -> - let o = Test(5) - sprintf "%O" o |> equal "10" - - testCase "sprintf \"%A\" with overloaded string works" <| fun () -> - let o = Test(5) - (sprintf "%A" o).Replace("\"", "") |> equal "10" + .Append("abc") + sb.Chars(-1) |> ignore + sb.Chars(3) |> ignore + + testCase "StringBuilder.Replace works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + .Append("abc") + .Replace('a', 'x') + .Replace("cx", "yz") + sb.ToString() |> equal "xbyzbc" + + testCase "StringBuilder index getter works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] |> equal 'b' + + testCase "StringBuilder index setter works" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] <- 'x' + sb.ToString() |> equal "axc" + + // Formatting + + testCase "kprintf works" <| fun () -> + let f (s:string) = s + "XX" + Printf.kprintf f "hello" |> equal "helloXX" + Printf.kprintf f "%X" 255 |> equal "FFXX" + Printf.kprintf f "%.2f %g" 0.5468989 5. |> equal "0.55 5XX" + + testCase "kprintf works indirectly" <| fun () -> // See #1204 + let lines = ResizeArray() + let linef fmt = Printf.ksprintf lines.Add fmt // broken + linef "open %s" "Foo" + lines |> Seq.toList |> equal ["open Foo"] + + testCase "kbprintf works" <| fun () -> + let sb = System.Text.StringBuilder() + let mutable i = 0 + let f () = i <- i + 1 + Printf.kbprintf f sb "Hello" + Printf.kbprintf f sb " %s!" "world" + i |> equal 2 + sb.ToString() |> equal "Hello world!" + + testCase "ksprintf curries correctly" <| fun () -> + let append (a: string) b = a + b + let step1 = Printf.ksprintf append "%d" + let step2 = step1 42 + let result = step2 "The answer is: " + result |> equal "42The answer is: " + + testCase "bprintf works" <| fun () -> + let sb = System.Text.StringBuilder(10) + Printf.bprintf sb "Hello" + Printf.bprintf sb " %s!" "world" + sb.ToString() |> equal "Hello world!" + + testCase "sprintf works" <| fun () -> + // Immediately applied + sprintf "%.2f %g" 0.5468989 5. + |> equal "0.55 5" + // Curried + let printer = sprintf "Hi %s, good %s!" + let printer = printer "Alfonso" + printer "morning" |> equal "Hi Alfonso, good morning!" + printer "evening" |> equal "Hi Alfonso, good evening!" + + testCase "sprintf works II" <| fun () -> + let printer2 = sprintf "Hi %s, good %s%s" "Maxime" + let printer2 = printer2 "afternoon" + printer2 "?" |> equal "Hi Maxime, good afternoon?" + + testCase "sprintf with different decimal digits works" <| fun () -> // See #1932 + sprintf "Percent: %.0f%%" 5.0 |> equal "Percent: 5%" + sprintf "Percent: %.2f%%" 5. |> equal "Percent: 5.00%" + sprintf "Percent: %.1f%%" 5.24 |> equal "Percent: 5.2%" + sprintf "Percent: %.2f%%" 5.268 |> equal "Percent: 5.27%" + sprintf "Percent: %f%%" 5.67 |> equal "Percent: 5.670000%" + + testCase "sprintf displays sign correctly" <| fun () -> // See #1937 + sprintf "%i" 1 |> equal "1" + sprintf "%d" 1 |> equal "1" + sprintf "%d" 1L |> equal "1" + sprintf "%.2f" 1. |> equal "1.00" + sprintf "%i" -1 |> equal "-1" + sprintf "%d" -1 |> equal "-1" + sprintf "%d" -1L |> equal "-1" + sprintf "%.2f" -1. |> equal "-1.00" + + testCase "format string can use and compose string literals" <| fun () -> + let renderedCoordinates = sprintf formatCoordinateBody 0.25 0.75 + let renderedText = sprintf fullFormat 0.25 0.75 + + equal "(0.250000,0.750000)" renderedCoordinates + equal "Person at coordinates(0.250000,0.750000)" renderedText + + testCase "Print.sprintf works" <| fun () -> // See #1216 + let res = Printf.sprintf "%s" "abc" + equal "res: abc" ("res: " + res) + + testCase "sprintf without arguments works" <| fun () -> + sprintf "hello" |> equal "hello" + + testCase "input of print format can be retrieved" <| fun () -> + let pathScan (pf:PrintfFormat<_,_,_,_,'t>) = + let formatStr = pf.Value + formatStr + + equal "/hello/%s" (pathScan "/hello/%s") + + testCase "sprintf with escaped percent symbols works" <| fun () -> // See #195 + let r, r1, r2 = "Ratio", 0.213849, 0.799898 + sprintf "%s1: %.2f%% %s2: %.2f%%" r (r1*100.) r (r2*100.) + |> equal "Ratio1: 21.38% Ratio2: 79.99%" + + testCase "sprintf with percent symbols in arguments works" <| fun () -> // See #329 + let same s = sprintf "%s" s |> equal s + same "%" + same "%%" + same "%%%" + same "%%%%" + same "% %" + same "%% %%" + same "%% % % %%" + + testCase "Fix #2398: Exception when two successive string format placeholders and value of first one ends in `%`" <| fun () -> + sprintf "%c%s" '%' "text" |> equal "%text" + + testCase "Unions with sprintf %A" <| fun () -> + Bar(1,5) |> sprintf "%A" |> equal "Bar (1, 5)" + Foo1 4.5 |> sprintf "%A" |> equal "Foo1 4.5" + Foo4 Foo3 |> sprintf "%A" |> equal "Foo4 Foo3" + Foo4(Foo1 4.5) |> sprintf "%A" |> equal "Foo4 (Foo1 4.5)" + Foo3 |> sprintf "%A" |> equal "Foo3" + + testCase "Unions with string operator" <| fun () -> + Bar(1,5) |> string |> equal "Bar (1, 5)" + Foo1 4.5 |> string |> equal "Foo1 4.5" + Foo4 Foo3 |> string |> equal "Foo4 Foo3" + Foo4(Foo1 4.5) |>string |> equal "Foo4 (Foo1 4.5)" + Foo3 |> string |> equal "Foo3" + + testCase "sprintf \"%O\" with overloaded string works" <| fun () -> + let o = Test(5) + sprintf "%O" o |> equal "10" + + testCase "sprintf \"%A\" with overloaded string works" <| fun () -> + let o = Test(5) + (sprintf "%A" o).Replace("\"", "") |> equal "10" #if FABLE_COMPILER - testCase "sprintf \"%A\" with circular references doesn't crash" <| fun () -> // See #338 - let o = obj() - o?self <- o - sprintf "%A" o |> ignore + testCase "sprintf \"%A\" with circular references doesn't crash" <| fun () -> // See #338 + let o = obj() + o?self <- o + sprintf "%A" o |> ignore #endif - testCase "F# nameof works" <| fun () -> - M.f 12 |> equal "x" - nameof M |> equal "M" - nameof M.f |> equal "f" - - testCase "string interpolation works" <| fun () -> - let name = "Phillip" - let age = 29 - sprintf $"Name: {name}, Age: %i{age}" - |> equal "Name: Phillip, Age: 29" - - testCase "string interpolation works with inline expressions" <| fun () -> - $"I think {3.0 + 0.14} is close to %.8f{Math.PI}!".Replace(",", ".") - |> equal "I think 3.14 is close to 3.14159265!" - - testCase "string interpolation works with anonymous records" <| fun () -> - let person = - {| Name = "John" - Surname = "Doe" - Age = 32 - Country = "The United Kingdom" |} - $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" - |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" - - testCase "Interpolated strings keep empty lines" <| fun () -> + testCase "string interpolation works" <| fun () -> + let name = "Phillip" + let age = 29 + sprintf $"Name: {name}, Age: %i{age}" + |> equal "Name: Phillip, Age: 29" + + testCase "string interpolation works with inline expressions" <| fun () -> + $"I think {3.0 + 0.14} is close to %.8f{Math.PI}!".Replace(",", ".") + |> equal "I think 3.14 is close to 3.14159265!" + + testCase "string interpolation works with anonymous records" <| fun () -> + let person = + {| + Name = "John" + Surname = "Doe" + Age = 32 + Country = "The United Kingdom" + |} + $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" + |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" + + testCase "Interpolated strings keep empty lines" <| fun () -> let s1 = $"""1 @@ -312,856 +326,856 @@ let tests = equal s1.Length s2.Length equal 13 s1.Length - testCase "Can use backslash is interpolated strings" <| fun () -> + testCase "Can use backslash is interpolated strings" <| fun () -> $"\n{1+1}\n" |> equal """ 2 """ - testCase "Backslash is escaped in interpolated strings" <| fun () -> // See #2649 - $"\\" |> equal @"\" - $"\\".Length |> equal 1 - $@"\" |> equal @"\" - $@"\".Length |> equal 1 - @$"\" |> equal @"\" - @$"\".Length |> equal 1 - $"\\{4}" |> equal @"\4" - $"\\{4}".Length |> equal 2 - $@"\{4}" |> equal @"\4" - $@"\{4}".Length |> equal 2 - @$"\{4}" |> equal @"\4" - @$"\{4}".Length |> equal 2 - - testCase "%O in interpolated strings works recursively" <| fun () -> // See #3078 - let b = B() - $"b=(%O{b})" |> equal "b=(a=6)" - $"%O{b}%O{b}" |> equal "a=7a=8" - - testCase "Extended string interpolation syntax" <| fun () -> - let classAttr = "item-panel" - let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}""" - cssNew |> equal ".item-panel:hover {background-color: #eee;}" - - testCase "sprintf \"%A\" with lists works" <| fun () -> - let xs = ["Hi"; "Hello"; "Hola"] - (sprintf "%A" xs).Replace("\"", "") |> equal "[Hi; Hello; Hola]" - - testCase "sprintf \"%A\" with nested lists works" <| fun () -> - let xs = [["Hi"]; ["Hello"]; ["Hola"]] - (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" - - testCase "sprintf \"%A\" with sequences works" <| fun () -> - let xs = seq { "Hi"; "Hello"; "Hola" } - sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true - - testCase "Storing result of Seq.tail and printing the result several times works. Related to #1996" <| fun () -> - let tweets = seq { "Hi"; "Hello"; "Hola" } - let tweetsTailR: seq = tweets |> Seq.tail - - let a = sprintf "%A" (tweetsTailR) - let b = sprintf "%A" (tweetsTailR) - - containsInOrder ["Hello"; "Hola"] a |> equal true - containsInOrder ["Hello"; "Hola"] b |> equal true - - testCase "sprintf \"%X\" works" <| fun () -> - //These should all be the Native JS Versions (except int64 / uint64) - //See #1530 for more information. - - sprintf "255: %X" 255 |> equal "255: FF" - sprintf "255: %x" 255 |> equal "255: ff" - sprintf "-255: %X" -255 |> equal "-255: FFFFFF01" - sprintf "4095L: %X" 4095L |> equal "4095L: FFF" - sprintf "-4095L: %X" -4095L |> equal "-4095L: FFFFFFFFFFFFF001" - sprintf "1 <<< 31: %x" (1 <<< 31) |> equal "1 <<< 31: 80000000" - sprintf "1u <<< 31: %x" (1u <<< 31) |> equal "1u <<< 31: 80000000" - sprintf "2147483649L: %x" 2147483649L |> equal "2147483649L: 80000001" - sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" - sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" - sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" - - testCase "sprintf integers with sign and padding works" <| fun () -> // See #1931 - sprintf "%+04i" 1 |> equal "+001" - sprintf "%+04i" -1 |> equal "-001" - sprintf "%5d" -5 |> equal " -5" - sprintf "%5d" -5L |> equal " -5" - sprintf "%- 4i" 5 |> equal " 5 " - - testCase "parameterized padding works" <| fun () -> // See #2336 - sprintf "[%*s][%*s]" 6 "Hello" 5 "Foo" - |> equal "[ Hello][ Foo]" - - testCase "String.Format should fail if there are less arguments than placeholders" <| fun () -> // See #2768 - throwsAnyError <| fun () -> String.Format ("Hello {0}", args = Array.empty) - - testCase "String.Format combining padding and zeroes pattern works" <| fun () -> - String.Format(CultureInfo.InvariantCulture, "{0:++0.00++}", -5000.5657) |> equal "-++5000.57++" - String.Format(CultureInfo.InvariantCulture, "{0:000.00}foo", 5) |> equal "005.00foo" - String.Format(CultureInfo.InvariantCulture, "{0,-8:000.00}foo", 12.456) |> equal "012.46 foo" - - testCase "String.Format {0:x} works" <| fun () -> - //See above comment on expected values - String.Format(CultureInfo.InvariantCulture, "255: {0:X}", 255) |> equal "255: FF" - String.Format(CultureInfo.InvariantCulture, "255: {0:x}", 255) |> equal "255: ff" - String.Format(CultureInfo.InvariantCulture, "-255: {0:X}", -255) |> equal "-255: FFFFFF01" - String.Format(CultureInfo.InvariantCulture, "4095L: {0:X}", 4095L) |> equal "4095L: FFF" - String.Format(CultureInfo.InvariantCulture, "-4095L: {0:X}", -4095L) |> equal "-4095L: FFFFFFFFFFFFF001" - String.Format(CultureInfo.InvariantCulture, "1 <<< 31: {0:x}", (1 <<< 31)) |> equal "1 <<< 31: 80000000" - String.Format(CultureInfo.InvariantCulture, "1u <<< 31: {0:x}", (1u <<< 31)) |> equal "1u <<< 31: 80000000" - String.Format(CultureInfo.InvariantCulture, "2147483649L: {0:x}", 2147483649L) |> equal "2147483649L: 80000001" - String.Format(CultureInfo.InvariantCulture, "2147483650uL: {0:x}", 2147483650uL) |> equal "2147483650uL: 80000002" - String.Format(CultureInfo.InvariantCulture, "1L <<< 63: {0:x}", (1L <<< 63)) |> equal "1L <<< 63: 8000000000000000" - String.Format(CultureInfo.InvariantCulture, "1uL <<< 63: {0:x}", (1uL <<< 63)) |> equal "1uL <<< 63: 8000000000000000" - - testCase "String.Format {0:x} with precision works" <| fun () -> - String.Format(CultureInfo.InvariantCulture, "#{0:X3}", 0xC149D) |> equal "#C149D" - String.Format(CultureInfo.InvariantCulture, "#{0:X6}", 0xC149D) |> equal "#0C149D" - - testCase "String.Format works with thousands separator" <| fun () -> - String.Format(CultureInfo.InvariantCulture, "{0}", 12343235354.6547757) |> equal "12343235354.654776" - String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757) |> equal "12,343,235,354.655" - String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757M) |> equal "12,343,235,354.655" - String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456) |> equal "123.46" - String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456M) |> equal "123.46" - String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123438192123.456M) |> equal "123,438,192,123.46" - String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 1.456M) |> equal "1" - String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1.456M) |> equal "01" - - testCase "String.Format can omit decimal digits" <| fun () -> - String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 12343235354.6547757) |> equal "12,343,235,355" - String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.6547757) |> equal "12,343,235,355" - String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.) |> equal "12,343,235,354" - String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354.) |> equal "12343235354" - String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354.) |> equal "12343235354" - - String.Format(CultureInfo.InvariantCulture, "{0:#,#}", 12343235354.6547757M) |> equal "12,343,235,355" - String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354.6547757M) |> equal "12,343,235,355" - - String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1234323535) |> equal "1,234,323,535" - String.Format(CultureInfo.InvariantCulture, "{0:#}", 1234323535) |> equal "1234323535" - String.Format(CultureInfo.InvariantCulture, "{0:0}", 1234323535) |> equal "1234323535" - - String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354M) |> equal "12,343,235,354" - String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 343235354M) |> equal "343,235,354" - String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354M) |> equal "12343235354" - String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354M) |> equal "12343235354" - - testCase "String.Format trims trailing zeroes when using # placeholder" <| fun () -> // Fix #2950 - String.Format(CultureInfo.InvariantCulture, "{0:######,###.000####}", -6789.5688) |> equal "-6,789.5688" - String.Format(CultureInfo.InvariantCulture, "{0:######,###.000####}", 6789.5688) |> equal "6,789.5688" - String.Format(CultureInfo.InvariantCulture, "{0:0######,###.000####0}", -6789.5688) |> equal "-0,000,006,789.56880000" - String.Format(CultureInfo.InvariantCulture, "{0:0######,###.000####0}", 6789.5688) |> equal "0,000,006,789.56880000" - - testCase "ToString formatted works with decimals" <| fun () -> // See #2276 - let decimal = 78.6M - decimal.ToString("0.000").Replace(",", ".") |> equal "78.600" - - testCase "Printf works with generic argument" <| fun () -> - spr "bar %s" "a" |> equal "bar a" - spr "foo %i %i" 3 5 |> equal "foo 3 5" - let f1 = spr "foo %i %i" - let f2 = f1 2 - f2 2 |> equal "foo 2 2" - let f1 = spr "foo %i %i %i" - let f2 = f1 2 - let f3 = f2 2 - f3 2 |> equal "foo 2 2 2" - - testCase "Printf in sequence is not erased" <| fun () -> - let x = sprintf "Foo" - let y = sprintf "B%sr" "a" - x + y |> equal "FooBar" - - testCase "String slicing works" <| fun () -> - let s = "cat and dog" - sprintf "%s" s.[2..8] |> equal "t and d" - sprintf "%s" s.[2..] |> equal "t and dog" - sprintf "%s" s.[..8] |> equal "cat and d" - - testCase "String.Format works" <| fun () -> - let arg1, arg2, arg3 = "F#", "Fable", "Babel" - String.Format(CultureInfo.InvariantCulture, "{2} is to {1} what {1} is to {0}", arg1, arg2, arg3) - |> equal "Babel is to Fable what Fable is to F#" - - testCase "String.Format with extra formatting works" <| fun () -> - let i = 0.5466788 - let dt = DateTime(2014, 9, 26).AddMinutes(19.) - String.Format(CultureInfo.InvariantCulture, "{0:F2} {0:P2} {1:yyyy-MM-dd HH:mm}", i, dt) - .Replace(",", ".").Replace(" %", "%") - |> equal "0.55 54.67% 2014-09-26 00:19" - - testCase "Padding works" <| fun () -> - "3.14".PadLeft(10) |> equal " 3.14" - "3.14".PadRight(10) |> equal "3.14 " - "22".PadLeft(10, '0') |> equal "0000000022" - "-22".PadRight(10, 'X') |> equal "-22XXXXXXX" - "333".PadLeft(1) |> equal "333" - - testCase "Padding with sprintf works" <| fun () -> - sprintf "%10.1f" 3.14 |> equal " 3.1" - sprintf "%-10.1f" 3.14 |> equal "3.1 " - sprintf "%+010i" 22 |> equal "+000000022" - sprintf "%+0-10i" -22 |> equal "-22 " - - testCase "Padding with String.Format works" <| fun () -> - String.Format(CultureInfo.InvariantCulture, "{0,10:F1}", 3.14) |> equal " 3.1" - String.Format(CultureInfo.InvariantCulture, "{0,-10:F1}", 3.14) |> equal "3.1 " - String.Format(CultureInfo.InvariantCulture, "{0,10}", 22) |> equal " 22" - String.Format(CultureInfo.InvariantCulture, "{0,-10}", -22) |> equal "-22 " - - // Conversions - - testCase "Conversion char to int works" <| fun () -> - equal 97 (int 'a') - equal 'a' (char 97) - - testCase "Conversion string to char works" <| fun () -> - equal 'a' (char "a") - equal "a" (string 'a') - - testCase "Conversion string to negative int8 works" <| fun () -> - equal -5y (int8 "-5") - equal "-5" (string -5y) - - testCase "Conversion string to negative int16 works" <| fun () -> - equal -5s (int16 "-5") - equal "-5" (string -5s) - - testCase "Conversion string to negative int32 works" <| fun () -> - equal -5 (int32 "-5") - equal "-5" (string -5) - - testCase "Conversion string to negative int64 works" <| fun () -> - equal -5L (int64 "-5") - equal "-5" (string -5L) - - testCase "Conversion string to int8 works" <| fun () -> - equal 5y (int8 "5") - equal "5" (string 5y) - - testCase "Conversion string to int16 works" <| fun () -> - equal 5s (int16 "5") - equal "5" (string 5s) - - testCase "Conversion string to int32 works" <| fun () -> - equal 5 (int32 "5") - equal "5" (string 5) - - testCase "Conversion string to int64 works" <| fun () -> - equal 5L (int64 "5") - equal "5" (string 5L) - - testCase "Conversion string to uint8 works" <| fun () -> - equal 5uy (uint8 "5") - equal "5" (string 5uy) - - testCase "Conversion string to uint16 works" <| fun () -> - equal 5us (uint16 "5") - equal "5" (string 5us) - - testCase "Conversion string to uint32 works" <| fun () -> - equal 5u (uint32 "5") - equal "5" (string 5u) - - testCase "Conversion string to uint64 works" <| fun () -> - equal 5uL (uint64 "5") - equal "5" (string 5uL) - - testCase "Conversion string to single works" <| fun () -> - equal 5.f (float32 "5.0") - equal -5.f (float32 "-5.0") - (string 5.f).StartsWith("5") |> equal true - equal 5.25f (float32 "5.25") - (string 5.25f).StartsWith("5.25") |> equal true - - testCase "Conversion string to double works" <| fun () -> - equal 5. (float "5.0") - equal -5. (float "-5.0") - (string 5.).StartsWith("5") |> equal true - equal 5.25 (float "5.25") - (string 5.25).StartsWith("5.25") |> equal true - - testCase "Conversion string to decimal works" <| fun () -> - equal 5.m (decimal "5.0") - equal -5.m (decimal "-5.0") - (string 5.m).StartsWith("5") |> equal true - equal 5.25m (decimal "5.25") - (string 5.25m).StartsWith("5.25") |> equal true - - // System.String - constructors - - testCase "String.ctor(char[]) works" <| fun () -> - System.String([|'f'; 'a'; 'b'; 'l'; 'e'|]) - |> equal "fable" - - testCase "String.ctor(char, int) works" <| fun () -> - System.String('f', 5) - |> equal "fffff" - - testCase "String.ctor(char[], int, int) works" <| fun () -> - System.String([|'f'; 'a'; 'b'; 'l'; 'e'|], 1, 3) - |> equal "abl" - - // System.String - static methods - - testCase "System.String.Equals works" <| fun () -> - System.String.Equals("abc", "abc") |> equal true - // TypeScript will complain if we use equality with two different literals - let mutable a = "a" // make it mutable so Fable doesn't inline it - System.String.Equals("ABC", a + "bc") |> equal false - System.String.Equals("abc", a + "bd") |> equal false - "abc".Equals("abc") |> equal true - "ABC".Equals(a + "bc") |> equal false - "abc".Equals(a + "bd") |> equal false - System.String.Equals("ABC", "abc", StringComparison.Ordinal) |> equal false - System.String.Equals("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal true - "ABC".Equals("abc", StringComparison.Ordinal) |> equal false - "ABC".Equals("abc", StringComparison.OrdinalIgnoreCase) |> equal true - - testCase "String.Compare works" <| fun () -> - "ABC".CompareTo("abc") > 0 |> equal true - System.String.Compare("abc", "abc") |> equal 0 - System.String.Compare("ABC", "abc") |> equal 1 - System.String.Compare("abc", "abd") |> equal -1 - System.String.Compare("bbc", "abd") |> equal 1 - System.String.Compare("ABC", "abc", false) |> equal 1 - System.String.Compare("ABC", "abc", true) |> equal 0 - System.String.Compare("ABC", "abd", true) |> equal -1 - System.String.Compare("BBC", "abd", true) |> equal 1 - System.String.Compare("ABC", "abc", StringComparison.CurrentCulture) > 0 |> equal true - System.String.Compare("ABC", "abc", StringComparison.Ordinal) < 0 |> equal true - System.String.Compare("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal 0 - System.String.Compare("abc", 0, "bcd", 0, 3) |> equal -1 - System.String.Compare("abc", 1, "bcd", 0, 2) |> equal 0 - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCulture) > 0 |> equal true - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.Ordinal) < 0 |> equal true - System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.OrdinalIgnoreCase) |> equal 0 - - testCase "String.IsNullOrEmpty works" <| fun () -> - let args = [("", true); (null, true); ("test", false); (" \t", false)] - for arg in args do - System.String.IsNullOrEmpty(fst arg) - |> equal (snd arg) - - testCase "String.IsNullOrWhiteSpace works" <| fun () -> - let args = [("", true); (null, true); ("test", false); (" \t", true)] - for arg in args do - System.String.IsNullOrWhiteSpace(fst arg) - |> equal (snd arg) - - // System.String - instance methods - - testCase "String.Contains works" <| fun () -> - "ABC".Contains("B") |> equal true - "ABC".Contains("Z") |> equal false - - testCase "String.Split works" <| fun () -> - "a b c d".Split(' ') - |> equal [|"a";"b";"c";"";"d"|] - "a b c d ".Split() - |> equal [|"a";"b";"c";"";"d";""|] - "a-b-c".Split() - |> equal [|"a-b-c"|] - "a-b-c".Split("") - |> equal [|"a-b-c"|] - "a\tb".Split() - |> equal [|"a";"b"|] - "a\nb".Split() - |> equal [|"a";"b"|] - "a\rb".Split() - |> equal [|"a";"b"|] - "a\u2003b".Split() // em space - |> equal [|"a";"b"|] - "a b c d".Split(null) - |> equal [|"a";"b";"c";"";"d"|] - "a\tb".Split(null) - |> equal [|"a";"b"|] - "a\u2003b".Split(null) // em space - |> equal [|"a";"b"|] - let array = "a;b,c".Split(',', ';') - "abc" = array.[0] + array.[1] + array.[2] - |> equal true - "a--b-c".Split([|"--"|], StringSplitOptions.None) - |> equal [|"a";"b-c"|] - " a-- b- c ".Split('-', 2, StringSplitOptions.None) - |> equal [|" a"; "- b- c "|] - "---o---o---".Split("--", StringSplitOptions.None) - |> equal [|""; "-o"; "-o"; "-"|]; - - testCase "String.Split with remove empties works" <| fun () -> - "a b c d ".Split([|" "|], StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"a";"b";"c";"d"|] |> equal true - " a-- b- c ".Split("-", 2, StringSplitOptions.RemoveEmptyEntries) - |> (=) [|" a"; " b- c "|] |> equal true - "---o---o---".Split("--", StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"-o"; "-o"; "-"|] |> equal true - let array = ";,a;b,c".Split([|','; ';'|], StringSplitOptions.RemoveEmptyEntries) - "abc" = array.[0] + array.[1] + array.[2] - |> equal true - - testCase "String.Split with count works" <| fun () -> - let array = "a b c d".Split ([|' '|], 2) - equal "a" array.[0] - equal "b c d" array.[1] - "a;,b,c;d".Split([|','; ';'|], 3, StringSplitOptions.RemoveEmptyEntries) - |> (=) [|"a";"b";"c;d"|] |> equal true - "a-b-c".Split("", System.Int32.MaxValue) - |> (=) [|"a-b-c"|] |> equal true - - testCase "String.Split with empty works" <| fun () -> - let array = "a b cd".Split() - array |> equal [| "a"; "b"; "cd" |] - - testCase "String.Split with trim entries works" <| fun () -> - " a-- b- c ".Split('-', 2, StringSplitOptions.TrimEntries) - |> (=) [|"a"; "- b- c"|] |> equal true - " a-- b- c ".Split('-', 3, StringSplitOptions.TrimEntries) - |> (=) [|"a"; ""; "b- c"|] |> equal true - - testCase "String.Split with trim and remove entries works" <| fun () -> - " a-- b- c ".Split([| "-" |], 2, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [|"a"; "b- c"|] - " a-- b- c ".Split([| '-' |], 3, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [|"a"; "b"; "c"|] - - testCase "String.Split with consecutive separators works" <| fun () -> - " ".Split(" ", 4, StringSplitOptions.None) - |> equal [|"";"";"";" "|] - " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries) - |> equal [||] - " ".Split(" ", 4, StringSplitOptions.TrimEntries) - |> equal [|"";"";"";""|] - " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) - |> equal [||] - - testCase "String.Replace works" <| fun () -> - "abc abc abc".Replace("abc", "d") |> equal "d d d" - // String.Replace does not get stuck in endless loop - "...".Replace(".", "..") |> equal "......" - - testCase "Access char by index works" <| fun () -> - let c = "abcd".[2] - equal 'c' c - equal 'd' (char ((int c) + 1)) - - testCase "String.IndexOf char works" <| fun () -> - "abcd".IndexOf('b') * 100 + "abcd".IndexOf('e') - |> equal 99 - - testCase "String.IndexOf char works with offset" <| fun () -> - "abcdbc".IndexOf('b', 3) - |> equal 4 - - testCase "String.IndexOf with StringComparison" <| fun () -> - "abcdbc".IndexOf("b", StringComparison.Ordinal) - |> equal 1 - - testCase "String.IndexOf with index and StringComparison" <| fun () -> - "abcdbc".IndexOf("b", 3, StringComparison.Ordinal) - |> equal 4 - - testCase "String.LastIndexOf char works" <| fun () -> - "abcdbc".LastIndexOf('b') * 100 + "abcd".LastIndexOf('e') - |> equal 399 - - testCase "String.LastIndexOf char works with offset" <| fun () -> - "abcdbcebc".LastIndexOf('b', 3) - |> equal 1 - - testCase "String.LastIndexOf with StringComparison" <| fun () -> - "abcdbc".LastIndexOf("b", StringComparison.Ordinal) - |> equal 4 - - testCase "String.LastIndexOf with index and StringComparison" <| fun () -> - "abcdbc".LastIndexOf("b", 3, StringComparison.Ordinal) - |> equal 1 - - testCase "String.IndexOf works" <| fun () -> - "abcd".IndexOf("bc") * 100 + "abcd".IndexOf("bd") - |> equal 99 - - testCase "String.IndexOf works with offset" <| fun () -> - "abcdbc".IndexOf("bc", 3) - |> equal 4 - - testCase "String.LastIndexOf works" <| fun () -> - "abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd") - |> equal 399 - - testCase "String.LastIndexOf works with offset" <| fun () -> - "abcdbcebc".LastIndexOf("bc", 3) - |> equal 1 - - testCase "String.IndexOfAny works" <| fun () -> - "abcdbcebc".IndexOfAny([|'b'|]) |> equal 1 - "abcdbcebc".IndexOfAny([|'b'|], 2) |> equal 4 - "abcdbcebc".IndexOfAny([|'b'|], 2, 2) |> equal -1 - "abcdbcebc".IndexOfAny([|'f';'e'|]) |> equal 6 - "abcdbcebc".IndexOfAny([|'f';'e'|], 2) |> equal 6 - "abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1 - "abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1 - - testCase "String.StartsWith works" <| fun () -> - let args = [("ab", true); ("cd", false); ("abcdx", false)] - for arg in args do - "abcd".StartsWith(fst arg) - |> equal (snd arg) - - testCase "String.StartsWith with StringComparison works" <| fun () -> - let args = [("ab", true); ("cd", false); ("abcdx", false)] - for arg in args do - "ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase) - |> equal (snd arg) - - - testCase "String.EndsWith works" <| fun () -> - let args = [("ab", false); ("cd", true); ("abcdx", false)] - for arg in args do - "abcd".EndsWith(fst arg) - |> equal (snd arg) - - testCase "String.Trim works" <| fun () -> - " abc ".Trim() - |> equal "abc" - - testCase "String.Trim with chars works" <| fun () -> - @"\\\abc///".Trim('\\','/') - |> equal "abc" - - testCase "String.Trim with special chars works" <| fun () -> - @"()[]{}abc/.?*+-^$|\".Trim(@"()[]{}/.?*+-^$|\".ToCharArray()) - |> equal "abc" - - testCase "String.TrimStart works" <| fun () -> - "!!--abc ".TrimStart('!','-') - |> equal "abc " - - testCase "String.TrimStart with chars works" <| fun () -> - " abc ".TrimStart() - |> equal "abc " - - testCase "String.TrimEnd works" <| fun () -> - " abc ".TrimEnd() - |> equal " abc" - - testCase "String.TrimEnd with chars works" <| fun () -> - " abc??**".TrimEnd('*','?') - |> equal " abc" - @"\foo\bar\".Replace("\\", "/").TrimEnd('/') - |> equal "/foo/bar" - - testCase "String.Empty works" <| fun () -> - let s = String.Empty - s |> equal "" - - testCase "String.Chars works" <| fun () -> - let input = "hello" - input.Chars(2) - |> equal 'l' - - testCase "String.Substring works" <| fun () -> - "abcdefg".Substring(2) - |> equal "cdefg" - - testCase "String.Substring works with length" <| fun () -> - "abcdefg".Substring(2, 2) - |> equal "cd" - - testCase "String.Substring throws error if startIndex or length are out of bounds" <| fun () -> // See #1955 - let throws f = - try f () |> ignore; false - with _ -> true - throws (fun _ -> "abcdefg".Substring(20)) |> equal true - throws (fun _ -> "abcdefg".Substring(2, 10)) |> equal true - - testCase "String.ToUpper works" <| fun () -> - "AbC".ToUpper() |> equal "ABC" - - testCase "String.ToLower works" <| fun () -> - "aBc".ToLower() |> equal "abc" - - testCase "String.ToUpperInvariant works" <| fun () -> - "AbC".ToUpperInvariant() |> equal "ABC" - - testCase "String.ToLowerInvariant works" <| fun () -> - "aBc".ToLowerInvariant() |> equal "abc" - - testCase "String.Length works" <| fun () -> - "AbC".Length |> equal 3 - - testCase "String item works" <| fun () -> - "AbC".[1] |> equal 'b' - - testCase "String.ToCharArray works" <| fun () -> - let arr = "abcd".ToCharArray() - equal "c" (string arr.[2]) - arr |> Array.map (fun _ -> 1) |> Array.sum - |> equal arr.Length - - testCase "String enumeration handles surrogates pairs" <| fun () -> // See #1279 - let unicodeString = ".\U0001f404." - unicodeString |> List.ofSeq |> Seq.length |> equal 4 - String.length unicodeString |> equal 4 - let mutable len = 0 - for i in unicodeString do - len <- len + 1 - equal 4 len - - testCase "String.Join works" <| fun () -> - String.Join("--", "a", "b", "c") - |> equal "a--b--c" - String.Join("--", seq { yield "a"; yield "b"; yield "c" }) - |> equal "a--b--c" - - testCase "String.Join with indices works" <| fun () -> - String.Join("**", [|"a"; "b"; "c"; "d"|], 1, 2) - |> equal "b**c" - String.Join("*", [|"a"; "b"; "c"; "d"|], 1, 3) - |> equal "b*c*d" - - testCase "String.Join works with chars" <| fun () -> // See #1524 - String.Join("--", 'a', 'b', 'c') - |> equal "a--b--c" - String.Join("--", seq { yield 'a'; yield 'b'; yield 'c' }) - |> equal "a--b--c" - [0..10] - |> List.map (fun _ -> '*') - |> fun chars -> String.Join("", chars) - |> equal "***********" - - testCase "String.Join with big integers works" <| fun () -> - String.Join("--", [|3I; 5I|]) - |> equal "3--5" - String.Join("--", 3I, 5I) - |> equal "3--5" - - testCase "String.Join with single argument works" <| fun () -> // See #1182 - String.Join(",", "abc") |> equal "abc" - String.Join(",", [|"abc"|]) |> equal "abc" - String.Join(",", ["abc"]) |> equal "abc" - - testCase "System.String.Concat works" <| fun () -> - String.Concat("a", "b", "c") - |> equal "abc" - String.Concat(seq { yield "a"; yield "b"; yield "c" }) - |> equal "abc" - - testCase "System.String.Join with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.Join("", a) - s.Length |> equal n - - testCase "System.String.Join with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.Join("", a) - s.Length |> equal n - - testCase "System.String.Concat with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.Concat(a) - s.Length |> equal n - - testCase "System.String.Concat with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.Concat(a) - s.Length |> equal n - - testCase "String.concat with long array works" <| fun () -> - let n = 1_000_000 - let a = Array.init n (fun _i -> "a") - let s = String.concat "" a - s.Length |> equal n - - testCase "String.concat with long seq works" <| fun () -> - let n = 1_000_000 - let a = seq { for i in 1..n -> "a" } - let s = String.concat "" a - s.Length |> equal n - - testCase "String.Remove works" <| fun () -> - "abcd".Remove(2) - |> equal "ab" - "abcd".Remove(1,2) - |> equal "ad" - "abcd".Remove(0,2) - |> equal "cd" - "abcd".Remove(0,4) - |> equal "" - "abcd".Remove(0,0) - |> equal "abcd" - - testCase "String.Insert work" <| fun () -> - "foobar".Insert(3, " is ") - |> equal "foo is bar" - - testCase "Enumerating string works" <| fun () -> - let mutable res = "" - for c in "HELLO" |> Seq.rev do - res <- res + (string c) - equal "OLLEH" res - - // String - F# module functions - - testCase "String.concat works" <| fun () -> - String.concat "--" ["a"; "b"; "c"] |> equal "a--b--c" - seq { yield "a"; yield "b"; yield "c" } - |> String.concat "-" |> equal "a-b-c" - - testCase "String.forall and exists work" <| fun () -> - "!!!" |> String.forall (fun c -> c = '!') |> equal true - "a!a" |> String.forall (fun c -> c = '!') |> equal false - "aaa" |> String.forall (fun c -> c = '!') |> equal false - - testCase "String.init works" <| fun () -> - String.init 3 (fun i -> "a") - |> equal "aaa" - - testCase "String.collect works" <| fun () -> - "abc" |> String.collect (fun c -> "bcd") - |> equal "bcdbcdbcd" - - testCase "String.iter works" <| fun () -> - let res = ref "" - "Hello world!" - |> String.iter (fun c -> res := !res + c.ToString()) - equal "Hello world!" !res - - testCase "String.iteri works" <| fun () -> - let mutable res = "" - "Hello world!" - |> String.iteri (fun c i -> res <- res + i.ToString() + c.ToString()) - equal "H0e1l2l3o4 5w6o7r8l9d10!11" res - - testCase "String.length (function) works" <| fun () -> - "AbC" |> String.length - |> equal 3 - - testCase "String.map works" <| fun () -> - "Hello world!" |> String.map (fun c -> if c = 'H' then '_' else c) - |> equal "_ello world!" - - testCase "String.mapi works" <| fun () -> - "Hello world!" |> String.mapi (fun i c -> if i = 1 || c = 'H' then '_' else c) - |> equal "__llo world!" - - testCase "String.replicate works" <| fun () -> - String.replicate 3 "hi there" - |> equal "hi therehi therehi there" - - testCase "String.IsNullOrWhiteSpace works on string with blanks" <| fun () -> - String.IsNullOrWhiteSpace "Fri Jun 30 2017 12:30:00 GMT+0200 (Mitteleuropäische Sommerzeit)" - |> equal false - - - testCase "String.IsNullOrWhiteSpace works on blank only string" <| fun () -> - String.IsNullOrWhiteSpace " " - |> equal true - - testCase "String.filter works" <| fun () -> - String.filter (fun x -> x <> '.') "a.b.c" - |> equal "abc" - - testCase "String.filter works when predicate matches everything" <| fun () -> - String.filter (fun x -> x <> '.') "abc" - |> equal "abc" - - testCase "String.filter works when predicate doesn't match" <| fun () -> - String.filter (fun x -> x <> '.') "..." - |> equal "" - - testCase "String.filter works with empty string" <| fun () -> - String.filter (fun x -> x <> '.') "" - |> equal "" - - #if FABLE_COMPILER - testCase "System.Environment.NewLine works" <| fun () -> - System.Environment.NewLine - |> equal "\n" - #endif - - testCase "System.Uri.UnescapeDataString works" <| fun () -> + testCase "Backslash is escaped in interpolated strings" <| fun () -> // See #2649 + $"\\" |> equal @"\" + $"\\".Length |> equal 1 + $@"\" |> equal @"\" + $@"\".Length |> equal 1 + @$"\" |> equal @"\" + @$"\".Length |> equal 1 + $"\\{4}" |> equal @"\4" + $"\\{4}".Length |> equal 2 + $@"\{4}" |> equal @"\4" + $@"\{4}".Length |> equal 2 + @$"\{4}" |> equal @"\4" + @$"\{4}".Length |> equal 2 + + testCase "%O in interpolated strings works recursively" <| fun () -> // See #3078 + let b = B() + $"b=(%O{b})" |> equal "b=(a=6)" + $"%O{b}%O{b}" |> equal "a=7a=8" + + testCase "Extended string interpolation syntax" <| fun () -> + let classAttr = "item-panel" + let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}""" + cssNew |> equal ".item-panel:hover {background-color: #eee;}" + + testCase "sprintf \"%A\" with lists works" <| fun () -> + let xs = ["Hi"; "Hello"; "Hola"] + (sprintf "%A" xs).Replace("\"", "") |> equal "[Hi; Hello; Hola]" + + testCase "sprintf \"%A\" with nested lists works" <| fun () -> + let xs = [["Hi"]; ["Hello"]; ["Hola"]] + (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" + + testCase "sprintf \"%A\" with sequences works" <| fun () -> + let xs = seq { "Hi"; "Hello"; "Hola" } + sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true + + testCase "Storing result of Seq.tail and printing the result several times works. Related to #1996" <| fun () -> + let tweets = seq { "Hi"; "Hello"; "Hola" } + let tweetsTailR: seq = tweets |> Seq.tail + + let a = sprintf "%A" (tweetsTailR) + let b = sprintf "%A" (tweetsTailR) + + containsInOrder ["Hello"; "Hola"] a |> equal true + containsInOrder ["Hello"; "Hola"] b |> equal true + + testCase "sprintf \"%X\" works" <| fun () -> + //These should all be the Native JS Versions (except int64 / uint64) + //See #1530 for more information. + + sprintf "255: %X" 255 |> equal "255: FF" + sprintf "255: %x" 255 |> equal "255: ff" + sprintf "-255: %X" -255 |> equal "-255: FFFFFF01" + sprintf "4095L: %X" 4095L |> equal "4095L: FFF" + sprintf "-4095L: %X" -4095L |> equal "-4095L: FFFFFFFFFFFFF001" + sprintf "1 <<< 31: %x" (1 <<< 31) |> equal "1 <<< 31: 80000000" + sprintf "1u <<< 31: %x" (1u <<< 31) |> equal "1u <<< 31: 80000000" + sprintf "2147483649L: %x" 2147483649L |> equal "2147483649L: 80000001" + sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" + sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" + sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" + + testCase "sprintf integers with sign and padding works" <| fun () -> // See #1931 + sprintf "%+04i" 1 |> equal "+001" + sprintf "%+04i" -1 |> equal "-001" + sprintf "%5d" -5 |> equal " -5" + sprintf "%5d" -5L |> equal " -5" + sprintf "%- 4i" 5 |> equal " 5 " + + testCase "parameterized padding works" <| fun () -> // See #2336 + sprintf "[%*s][%*s]" 6 "Hello" 5 "Foo" + |> equal "[ Hello][ Foo]" + + testCase "String.Format should fail if there are less arguments than placeholders" <| fun () -> // See #2768 + throwsAnyError <| fun () -> String.Format ("Hello {0}", args = Array.empty) + + testCase "String.Format combining padding and zeroes pattern works" <| fun () -> + String.Format(CultureInfo.InvariantCulture, "{0:++0.00++}", -5000.5657) |> equal "-++5000.57++" + String.Format(CultureInfo.InvariantCulture, "{0:000.00}foo", 5) |> equal "005.00foo" + String.Format(CultureInfo.InvariantCulture, "{0,-8:000.00}foo", 12.456) |> equal "012.46 foo" + + testCase "String.Format {0:x} works" <| fun () -> + //See above comment on expected values + String.Format(CultureInfo.InvariantCulture, "255: {0:X}", 255) |> equal "255: FF" + String.Format(CultureInfo.InvariantCulture, "255: {0:x}", 255) |> equal "255: ff" + String.Format(CultureInfo.InvariantCulture, "-255: {0:X}", -255) |> equal "-255: FFFFFF01" + String.Format(CultureInfo.InvariantCulture, "4095L: {0:X}", 4095L) |> equal "4095L: FFF" + String.Format(CultureInfo.InvariantCulture, "-4095L: {0:X}", -4095L) |> equal "-4095L: FFFFFFFFFFFFF001" + String.Format(CultureInfo.InvariantCulture, "1 <<< 31: {0:x}", (1 <<< 31)) |> equal "1 <<< 31: 80000000" + String.Format(CultureInfo.InvariantCulture, "1u <<< 31: {0:x}", (1u <<< 31)) |> equal "1u <<< 31: 80000000" + String.Format(CultureInfo.InvariantCulture, "2147483649L: {0:x}", 2147483649L) |> equal "2147483649L: 80000001" + String.Format(CultureInfo.InvariantCulture, "2147483650uL: {0:x}", 2147483650uL) |> equal "2147483650uL: 80000002" + String.Format(CultureInfo.InvariantCulture, "1L <<< 63: {0:x}", (1L <<< 63)) |> equal "1L <<< 63: 8000000000000000" + String.Format(CultureInfo.InvariantCulture, "1uL <<< 63: {0:x}", (1uL <<< 63)) |> equal "1uL <<< 63: 8000000000000000" + + testCase "String.Format {0:x} with precision works" <| fun () -> + String.Format(CultureInfo.InvariantCulture, "#{0:X3}", 0xC149D) |> equal "#C149D" + String.Format(CultureInfo.InvariantCulture, "#{0:X6}", 0xC149D) |> equal "#0C149D" + + testCase "String.Format works with thousands separator" <| fun () -> + String.Format(CultureInfo.InvariantCulture, "{0}", 12343235354.6547757) |> equal "12343235354.654776" + String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757) |> equal "12,343,235,354.655" + String.Format(CultureInfo.InvariantCulture, "{0:#,##.000}", 12343235354.6547757M) |> equal "12,343,235,354.655" + String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456) |> equal "123.46" + String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123.456M) |> equal "123.46" + String.Format(CultureInfo.InvariantCulture, "{0:#,##.00}", 123438192123.456M) |> equal "123,438,192,123.46" + String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 1.456M) |> equal "1" + String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1.456M) |> equal "01" + + testCase "String.Format can omit decimal digits" <| fun () -> + String.Format(CultureInfo.InvariantCulture, "{0:#,##}", 12343235354.6547757) |> equal "12,343,235,355" + String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.6547757) |> equal "12,343,235,355" + String.Format(CultureInfo.InvariantCulture, "{0:0,00}", 12343235354.) |> equal "12,343,235,354" + String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354.) |> equal "12343235354" + String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354.) |> equal "12343235354" + + String.Format(CultureInfo.InvariantCulture, "{0:#,#}", 12343235354.6547757M) |> equal "12,343,235,355" + String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354.6547757M) |> equal "12,343,235,355" + + String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 1234323535) |> equal "1,234,323,535" + String.Format(CultureInfo.InvariantCulture, "{0:#}", 1234323535) |> equal "1234323535" + String.Format(CultureInfo.InvariantCulture, "{0:0}", 1234323535) |> equal "1234323535" + + String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 12343235354M) |> equal "12,343,235,354" + String.Format(CultureInfo.InvariantCulture, "{0:0,0}", 343235354M) |> equal "343,235,354" + String.Format(CultureInfo.InvariantCulture, "{0:#}", 12343235354M) |> equal "12343235354" + String.Format(CultureInfo.InvariantCulture, "{0:0}", 12343235354M) |> equal "12343235354" + + testCase "String.Format trims trailing zeroes when using # placeholder" <| fun () -> // Fix #2950 + String.Format(CultureInfo.InvariantCulture, "{0:######,###.000####}", -6789.5688) |> equal "-6,789.5688" + String.Format(CultureInfo.InvariantCulture, "{0:######,###.000####}", 6789.5688) |> equal "6,789.5688" + String.Format(CultureInfo.InvariantCulture, "{0:0######,###.000####0}", -6789.5688) |> equal "-0,000,006,789.56880000" + String.Format(CultureInfo.InvariantCulture, "{0:0######,###.000####0}", 6789.5688) |> equal "0,000,006,789.56880000" + + testCase "ToString formatted works with decimals" <| fun () -> // See #2276 + let decimal = 78.6M + decimal.ToString("0.000").Replace(",", ".") |> equal "78.600" + + testCase "Printf works with generic argument" <| fun () -> + spr "bar %s" "a" |> equal "bar a" + spr "foo %i %i" 3 5 |> equal "foo 3 5" + let f1 = spr "foo %i %i" + let f2 = f1 2 + f2 2 |> equal "foo 2 2" + let f1 = spr "foo %i %i %i" + let f2 = f1 2 + let f3 = f2 2 + f3 2 |> equal "foo 2 2 2" + + testCase "Printf in sequence is not erased" <| fun () -> + let x = sprintf "Foo" + let y = sprintf "B%sr" "a" + x + y |> equal "FooBar" + + testCase "String slicing works" <| fun () -> + let s = "cat and dog" + sprintf "%s" s.[2..8] |> equal "t and d" + sprintf "%s" s.[2..] |> equal "t and dog" + sprintf "%s" s.[..8] |> equal "cat and d" + + testCase "String.Format works" <| fun () -> + let arg1, arg2, arg3 = "F#", "Fable", "Babel" + String.Format(CultureInfo.InvariantCulture, "{2} is to {1} what {1} is to {0}", arg1, arg2, arg3) + |> equal "Babel is to Fable what Fable is to F#" + + testCase "String.Format with extra formatting works" <| fun () -> + let i = 0.5466788 + let dt = DateTime(2014, 9, 26).AddMinutes(19.) + String.Format(CultureInfo.InvariantCulture, "{0:F2} {0:P2} {1:yyyy-MM-dd HH:mm}", i, dt) + .Replace(",", ".").Replace(" %", "%") + |> equal "0.55 54.67% 2014-09-26 00:19" + + testCase "Padding works" <| fun () -> + "3.14".PadLeft(10) |> equal " 3.14" + "3.14".PadRight(10) |> equal "3.14 " + "22".PadLeft(10, '0') |> equal "0000000022" + "-22".PadRight(10, 'X') |> equal "-22XXXXXXX" + "333".PadLeft(1) |> equal "333" + + testCase "Padding with sprintf works" <| fun () -> + sprintf "%10.1f" 3.14 |> equal " 3.1" + sprintf "%-10.1f" 3.14 |> equal "3.1 " + sprintf "%+010i" 22 |> equal "+000000022" + sprintf "%+0-10i" -22 |> equal "-22 " + + testCase "Padding with String.Format works" <| fun () -> + String.Format(CultureInfo.InvariantCulture, "{0,10:F1}", 3.14) |> equal " 3.1" + String.Format(CultureInfo.InvariantCulture, "{0,-10:F1}", 3.14) |> equal "3.1 " + String.Format(CultureInfo.InvariantCulture, "{0,10}", 22) |> equal " 22" + String.Format(CultureInfo.InvariantCulture, "{0,-10}", -22) |> equal "-22 " + + // Conversions + + testCase "Conversion char to int works" <| fun () -> + equal 97 (int 'a') + equal 'a' (char 97) + + testCase "Conversion string to char works" <| fun () -> + equal 'a' (char "a") + equal "a" (string 'a') + + testCase "Conversion string to negative int8 works" <| fun () -> + equal -5y (int8 "-5") + equal "-5" (string -5y) + + testCase "Conversion string to negative int16 works" <| fun () -> + equal -5s (int16 "-5") + equal "-5" (string -5s) + + testCase "Conversion string to negative int32 works" <| fun () -> + equal -5 (int32 "-5") + equal "-5" (string -5) + + testCase "Conversion string to negative int64 works" <| fun () -> + equal -5L (int64 "-5") + equal "-5" (string -5L) + + testCase "Conversion string to int8 works" <| fun () -> + equal 5y (int8 "5") + equal "5" (string 5y) + + testCase "Conversion string to int16 works" <| fun () -> + equal 5s (int16 "5") + equal "5" (string 5s) + + testCase "Conversion string to int32 works" <| fun () -> + equal 5 (int32 "5") + equal "5" (string 5) + + testCase "Conversion string to int64 works" <| fun () -> + equal 5L (int64 "5") + equal "5" (string 5L) + + testCase "Conversion string to uint8 works" <| fun () -> + equal 5uy (uint8 "5") + equal "5" (string 5uy) + + testCase "Conversion string to uint16 works" <| fun () -> + equal 5us (uint16 "5") + equal "5" (string 5us) + + testCase "Conversion string to uint32 works" <| fun () -> + equal 5u (uint32 "5") + equal "5" (string 5u) + + testCase "Conversion string to uint64 works" <| fun () -> + equal 5uL (uint64 "5") + equal "5" (string 5uL) + + testCase "Conversion string to single works" <| fun () -> + equal 5.f (float32 "5.0") + equal -5.f (float32 "-5.0") + (string 5.f).StartsWith("5") |> equal true + equal 5.25f (float32 "5.25") + (string 5.25f).StartsWith("5.25") |> equal true + + testCase "Conversion string to double works" <| fun () -> + equal 5. (float "5.0") + equal -5. (float "-5.0") + (string 5.).StartsWith("5") |> equal true + equal 5.25 (float "5.25") + (string 5.25).StartsWith("5.25") |> equal true + + testCase "Conversion string to decimal works" <| fun () -> + equal 5.m (decimal "5.0") + equal -5.m (decimal "-5.0") + (string 5.m).StartsWith("5") |> equal true + equal 5.25m (decimal "5.25") + (string 5.25m).StartsWith("5.25") |> equal true + + // System.String - constructors + + testCase "String.ctor(char[]) works" <| fun () -> + System.String([|'f'; 'a'; 'b'; 'l'; 'e'|]) + |> equal "fable" + + testCase "String.ctor(char, int) works" <| fun () -> + System.String('f', 5) + |> equal "fffff" + + testCase "String.ctor(char[], int, int) works" <| fun () -> + System.String([|'f'; 'a'; 'b'; 'l'; 'e'|], 1, 3) + |> equal "abl" + + // System.String - static methods + + testCase "System.String.Equals works" <| fun () -> + System.String.Equals("abc", "abc") |> equal true + // TypeScript will complain if we use equality with two different literals + let mutable a = "a" // make it mutable so Fable doesn't inline it + System.String.Equals("ABC", a + "bc") |> equal false + System.String.Equals("abc", a + "bd") |> equal false + "abc".Equals("abc") |> equal true + "ABC".Equals(a + "bc") |> equal false + "abc".Equals(a + "bd") |> equal false + System.String.Equals("ABC", "abc", StringComparison.Ordinal) |> equal false + System.String.Equals("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal true + "ABC".Equals("abc", StringComparison.Ordinal) |> equal false + "ABC".Equals("abc", StringComparison.OrdinalIgnoreCase) |> equal true + + testCase "String.Compare works" <| fun () -> + "ABC".CompareTo("abc") > 0 |> equal true + System.String.Compare("abc", "abc") |> equal 0 + System.String.Compare("ABC", "abc") |> equal 1 + System.String.Compare("abc", "abd") |> equal -1 + System.String.Compare("bbc", "abd") |> equal 1 + System.String.Compare("ABC", "abc", false) |> equal 1 + System.String.Compare("ABC", "abc", true) |> equal 0 + System.String.Compare("ABC", "abd", true) |> equal -1 + System.String.Compare("BBC", "abd", true) |> equal 1 + System.String.Compare("ABC", "abc", StringComparison.CurrentCulture) > 0 |> equal true + System.String.Compare("ABC", "abc", StringComparison.Ordinal) < 0 |> equal true + System.String.Compare("ABC", "abc", StringComparison.OrdinalIgnoreCase) |> equal 0 + System.String.Compare("abc", 0, "bcd", 0, 3) |> equal -1 + System.String.Compare("abc", 1, "bcd", 0, 2) |> equal 0 + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.CurrentCulture) > 0 |> equal true + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.Ordinal) < 0 |> equal true + System.String.Compare("ABC", 1, "bcd", 0, 2, StringComparison.OrdinalIgnoreCase) |> equal 0 + + testCase "String.IsNullOrEmpty works" <| fun () -> + let args = [("", true); (null, true); ("test", false); (" \t", false)] + for arg in args do + System.String.IsNullOrEmpty(fst arg) + |> equal (snd arg) + + testCase "String.IsNullOrWhiteSpace works" <| fun () -> + let args = [("", true); (null, true); ("test", false); (" \t", true)] + for arg in args do + System.String.IsNullOrWhiteSpace(fst arg) + |> equal (snd arg) + + // System.String - instance methods + + testCase "String.Contains works" <| fun () -> + "ABC".Contains("B") |> equal true + "ABC".Contains("Z") |> equal false + + testCase "String.Split works" <| fun () -> + "a b c d".Split(' ') + |> equal [|"a";"b";"c";"";"d"|] + "a b c d ".Split() + |> equal [|"a";"b";"c";"";"d";""|] + "a-b-c".Split() + |> equal [|"a-b-c"|] + "a-b-c".Split("") + |> equal [|"a-b-c"|] + "a\tb".Split() + |> equal [|"a";"b"|] + "a\nb".Split() + |> equal [|"a";"b"|] + "a\rb".Split() + |> equal [|"a";"b"|] + "a\u2003b".Split() // em space + |> equal [|"a";"b"|] + "a b c d".Split(null) + |> equal [|"a";"b";"c";"";"d"|] + "a\tb".Split(null) + |> equal [|"a";"b"|] + "a\u2003b".Split(null) // em space + |> equal [|"a";"b"|] + let array = "a;b,c".Split(',', ';') + "abc" = array.[0] + array.[1] + array.[2] + |> equal true + "a--b-c".Split([|"--"|], StringSplitOptions.None) + |> equal [|"a";"b-c"|] + " a-- b- c ".Split('-', 2, StringSplitOptions.None) + |> equal [|" a"; "- b- c "|] + "---o---o---".Split("--", StringSplitOptions.None) + |> equal [|""; "-o"; "-o"; "-"|]; + + testCase "String.Split with remove empties works" <| fun () -> + "a b c d ".Split([|" "|], StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"a";"b";"c";"d"|] |> equal true + " a-- b- c ".Split("-", 2, StringSplitOptions.RemoveEmptyEntries) + |> (=) [|" a"; " b- c "|] |> equal true + "---o---o---".Split("--", StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"-o"; "-o"; "-"|] |> equal true + let array = ";,a;b,c".Split([|','; ';'|], StringSplitOptions.RemoveEmptyEntries) + "abc" = array.[0] + array.[1] + array.[2] + |> equal true + + testCase "String.Split with count works" <| fun () -> + let array = "a b c d".Split ([|' '|], 2) + equal "a" array.[0] + equal "b c d" array.[1] + "a;,b,c;d".Split([|','; ';'|], 3, StringSplitOptions.RemoveEmptyEntries) + |> (=) [|"a";"b";"c;d"|] |> equal true + "a-b-c".Split("", System.Int32.MaxValue) + |> (=) [|"a-b-c"|] |> equal true + + testCase "String.Split with empty works" <| fun () -> + let array = "a b cd".Split() + array |> equal [| "a"; "b"; "cd" |] + + testCase "String.Split with trim entries works" <| fun () -> + " a-- b- c ".Split('-', 2, StringSplitOptions.TrimEntries) + |> (=) [|"a"; "- b- c"|] |> equal true + " a-- b- c ".Split('-', 3, StringSplitOptions.TrimEntries) + |> (=) [|"a"; ""; "b- c"|] |> equal true + + testCase "String.Split with trim and remove entries works" <| fun () -> + " a-- b- c ".Split([| "-" |], 2, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [|"a"; "b- c"|] + " a-- b- c ".Split([| '-' |], 3, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [|"a"; "b"; "c"|] + + testCase "String.Split with consecutive separators works" <| fun () -> + " ".Split(" ", 4, StringSplitOptions.None) + |> equal [|"";"";"";" "|] + " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries) + |> equal [||] + " ".Split(" ", 4, StringSplitOptions.TrimEntries) + |> equal [|"";"";"";""|] + " ".Split(" ", 4, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) + |> equal [||] + + testCase "String.Replace works" <| fun () -> + "abc abc abc".Replace("abc", "d") |> equal "d d d" + // String.Replace does not get stuck in endless loop + "...".Replace(".", "..") |> equal "......" + + testCase "Access char by index works" <| fun () -> + let c = "abcd".[2] + equal 'c' c + equal 'd' (char ((int c) + 1)) + + testCase "String.IndexOf char works" <| fun () -> + "abcd".IndexOf('b') * 100 + "abcd".IndexOf('e') + |> equal 99 + + testCase "String.IndexOf char works with offset" <| fun () -> + "abcdbc".IndexOf('b', 3) + |> equal 4 + + testCase "String.IndexOf with StringComparison" <| fun () -> + "abcdbc".IndexOf("b", StringComparison.Ordinal) + |> equal 1 + + testCase "String.IndexOf with index and StringComparison" <| fun () -> + "abcdbc".IndexOf("b", 3, StringComparison.Ordinal) + |> equal 4 + + testCase "String.LastIndexOf char works" <| fun () -> + "abcdbc".LastIndexOf('b') * 100 + "abcd".LastIndexOf('e') + |> equal 399 + + testCase "String.LastIndexOf char works with offset" <| fun () -> + "abcdbcebc".LastIndexOf('b', 3) + |> equal 1 + + testCase "String.LastIndexOf with StringComparison" <| fun () -> + "abcdbc".LastIndexOf("b", StringComparison.Ordinal) + |> equal 4 + + testCase "String.LastIndexOf with index and StringComparison" <| fun () -> + "abcdbc".LastIndexOf("b", 3, StringComparison.Ordinal) + |> equal 1 + + testCase "String.IndexOf works" <| fun () -> + "abcd".IndexOf("bc") * 100 + "abcd".IndexOf("bd") + |> equal 99 + + testCase "String.IndexOf works with offset" <| fun () -> + "abcdbc".IndexOf("bc", 3) + |> equal 4 + + testCase "String.LastIndexOf works" <| fun () -> + "abcdbc".LastIndexOf("bc") * 100 + "abcd".LastIndexOf("bd") + |> equal 399 + + testCase "String.LastIndexOf works with offset" <| fun () -> + "abcdbcebc".LastIndexOf("bc", 3) + |> equal 1 + + testCase "String.IndexOfAny works" <| fun () -> + "abcdbcebc".IndexOfAny([|'b'|]) |> equal 1 + "abcdbcebc".IndexOfAny([|'b'|], 2) |> equal 4 + "abcdbcebc".IndexOfAny([|'b'|], 2, 2) |> equal -1 + "abcdbcebc".IndexOfAny([|'f';'e'|]) |> equal 6 + "abcdbcebc".IndexOfAny([|'f';'e'|], 2) |> equal 6 + "abcdbcebc".IndexOfAny([|'f';'e'|], 2, 4) |> equal -1 + "abcdbcebc".IndexOfAny([|'c';'b'|]) |> equal 1 + + testCase "String.StartsWith works" <| fun () -> + let args = [("ab", true); ("cd", false); ("abcdx", false)] + for arg in args do + "abcd".StartsWith(fst arg) + |> equal (snd arg) + + testCase "String.StartsWith with StringComparison works" <| fun () -> + let args = [("ab", true); ("cd", false); ("abcdx", false)] + for arg in args do + "ABCD".StartsWith(fst arg, StringComparison.OrdinalIgnoreCase) + |> equal (snd arg) + + + testCase "String.EndsWith works" <| fun () -> + let args = [("ab", false); ("cd", true); ("abcdx", false)] + for arg in args do + "abcd".EndsWith(fst arg) + |> equal (snd arg) + + testCase "String.Trim works" <| fun () -> + " abc ".Trim() + |> equal "abc" + + testCase "String.Trim with chars works" <| fun () -> + @"\\\abc///".Trim('\\','/') + |> equal "abc" + + testCase "String.Trim with special chars works" <| fun () -> + @"()[]{}abc/.?*+-^$|\".Trim(@"()[]{}/.?*+-^$|\".ToCharArray()) + |> equal "abc" + + testCase "String.TrimStart works" <| fun () -> + "!!--abc ".TrimStart('!','-') + |> equal "abc " + + testCase "String.TrimStart with chars works" <| fun () -> + " abc ".TrimStart() + |> equal "abc " + + testCase "String.TrimEnd works" <| fun () -> + " abc ".TrimEnd() + |> equal " abc" + + testCase "String.TrimEnd with chars works" <| fun () -> + " abc??**".TrimEnd('*','?') + |> equal " abc" + @"\foo\bar\".Replace("\\", "/").TrimEnd('/') + |> equal "/foo/bar" + + testCase "String.Empty works" <| fun () -> + let s = String.Empty + s |> equal "" + + testCase "String.Chars works" <| fun () -> + let input = "hello" + input.Chars(2) + |> equal 'l' + + testCase "String.Substring works" <| fun () -> + "abcdefg".Substring(2) + |> equal "cdefg" + + testCase "String.Substring works with length" <| fun () -> + "abcdefg".Substring(2, 2) + |> equal "cd" + + testCase "String.Substring throws error if startIndex or length are out of bounds" <| fun () -> // See #1955 + let throws f = + try f () |> ignore; false + with _ -> true + throws (fun _ -> "abcdefg".Substring(20)) |> equal true + throws (fun _ -> "abcdefg".Substring(2, 10)) |> equal true + + testCase "String.ToUpper works" <| fun () -> + "AbC".ToUpper() |> equal "ABC" + + testCase "String.ToLower works" <| fun () -> + "aBc".ToLower() |> equal "abc" + + testCase "String.ToUpperInvariant works" <| fun () -> + "AbC".ToUpperInvariant() |> equal "ABC" + + testCase "String.ToLowerInvariant works" <| fun () -> + "aBc".ToLowerInvariant() |> equal "abc" + + testCase "String.Length works" <| fun () -> + "AbC".Length |> equal 3 + + testCase "String item works" <| fun () -> + "AbC".[1] |> equal 'b' + + testCase "String.ToCharArray works" <| fun () -> + let arr = "abcd".ToCharArray() + equal "c" (string arr.[2]) + arr |> Array.map (fun _ -> 1) |> Array.sum + |> equal arr.Length + + testCase "String enumeration handles surrogates pairs" <| fun () -> // See #1279 + let unicodeString = ".\U0001f404." + unicodeString |> List.ofSeq |> Seq.length |> equal 4 + String.length unicodeString |> equal 4 + let mutable len = 0 + for i in unicodeString do + len <- len + 1 + equal 4 len + + testCase "String.Join works" <| fun () -> + String.Join("--", "a", "b", "c") + |> equal "a--b--c" + String.Join("--", seq { yield "a"; yield "b"; yield "c" }) + |> equal "a--b--c" + + testCase "String.Join with indices works" <| fun () -> + String.Join("**", [|"a"; "b"; "c"; "d"|], 1, 2) + |> equal "b**c" + String.Join("*", [|"a"; "b"; "c"; "d"|], 1, 3) + |> equal "b*c*d" + + testCase "String.Join works with chars" <| fun () -> // See #1524 + String.Join("--", 'a', 'b', 'c') + |> equal "a--b--c" + String.Join("--", seq { yield 'a'; yield 'b'; yield 'c' }) + |> equal "a--b--c" + [0..10] + |> List.map (fun _ -> '*') + |> fun chars -> String.Join("", chars) + |> equal "***********" + + testCase "String.Join with big integers works" <| fun () -> + String.Join("--", [|3I; 5I|]) + |> equal "3--5" + String.Join("--", 3I, 5I) + |> equal "3--5" + + testCase "String.Join with single argument works" <| fun () -> // See #1182 + String.Join(",", "abc") |> equal "abc" + String.Join(",", [|"abc"|]) |> equal "abc" + String.Join(",", ["abc"]) |> equal "abc" + + testCase "System.String.Concat works" <| fun () -> + String.Concat("a", "b", "c") + |> equal "abc" + String.Concat(seq { yield "a"; yield "b"; yield "c" }) + |> equal "abc" + + testCase "System.String.Join with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.Join("", a) + s.Length |> equal n + + testCase "System.String.Join with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.Join("", a) + s.Length |> equal n + + testCase "System.String.Concat with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.Concat(a) + s.Length |> equal n + + testCase "System.String.Concat with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.Concat(a) + s.Length |> equal n + + testCase "String.concat with long array works" <| fun () -> + let n = 1_000_000 + let a = Array.init n (fun _i -> "a") + let s = String.concat "" a + s.Length |> equal n + + testCase "String.concat with long seq works" <| fun () -> + let n = 1_000_000 + let a = seq { for i in 1..n -> "a" } + let s = String.concat "" a + s.Length |> equal n + + testCase "String.Remove works" <| fun () -> + "abcd".Remove(2) + |> equal "ab" + "abcd".Remove(1,2) + |> equal "ad" + "abcd".Remove(0,2) + |> equal "cd" + "abcd".Remove(0,4) + |> equal "" + "abcd".Remove(0,0) + |> equal "abcd" + + testCase "String.Insert work" <| fun () -> + "foobar".Insert(3, " is ") + |> equal "foo is bar" + + testCase "Enumerating string works" <| fun () -> + let mutable res = "" + for c in "HELLO" |> Seq.rev do + res <- res + (string c) + equal "OLLEH" res + + // String - F# module functions + + testCase "String.concat works" <| fun () -> + String.concat "--" ["a"; "b"; "c"] |> equal "a--b--c" + seq { yield "a"; yield "b"; yield "c" } + |> String.concat "-" |> equal "a-b-c" + + testCase "String.forall and exists work" <| fun () -> + "!!!" |> String.forall (fun c -> c = '!') |> equal true + "a!a" |> String.forall (fun c -> c = '!') |> equal false + "aaa" |> String.forall (fun c -> c = '!') |> equal false + + testCase "String.init works" <| fun () -> + String.init 3 (fun i -> "a") + |> equal "aaa" + + testCase "String.collect works" <| fun () -> + "abc" |> String.collect (fun c -> "bcd") + |> equal "bcdbcdbcd" + + testCase "String.iter works" <| fun () -> + let res = ref "" + "Hello world!" + |> String.iter (fun c -> res := !res + c.ToString()) + equal "Hello world!" !res + + testCase "String.iteri works" <| fun () -> + let mutable res = "" + "Hello world!" + |> String.iteri (fun c i -> res <- res + i.ToString() + c.ToString()) + equal "H0e1l2l3o4 5w6o7r8l9d10!11" res + + testCase "String.length (function) works" <| fun () -> + "AbC" |> String.length + |> equal 3 + + testCase "String.map works" <| fun () -> + "Hello world!" |> String.map (fun c -> if c = 'H' then '_' else c) + |> equal "_ello world!" + + testCase "String.mapi works" <| fun () -> + "Hello world!" |> String.mapi (fun i c -> if i = 1 || c = 'H' then '_' else c) + |> equal "__llo world!" + + testCase "String.replicate works" <| fun () -> + String.replicate 3 "hi there" + |> equal "hi therehi therehi there" + + testCase "String.IsNullOrWhiteSpace works on string with blanks" <| fun () -> + String.IsNullOrWhiteSpace "Fri Jun 30 2017 12:30:00 GMT+0200 (Mitteleuropäische Sommerzeit)" + |> equal false + + + testCase "String.IsNullOrWhiteSpace works on blank only string" <| fun () -> + String.IsNullOrWhiteSpace " " + |> equal true + + testCase "String.filter works" <| fun () -> + String.filter (fun x -> x <> '.') "a.b.c" + |> equal "abc" + + testCase "String.filter works when predicate matches everything" <| fun () -> + String.filter (fun x -> x <> '.') "abc" + |> equal "abc" + + testCase "String.filter works when predicate doesn't match" <| fun () -> + String.filter (fun x -> x <> '.') "..." + |> equal "" + + testCase "String.filter works with empty string" <| fun () -> + String.filter (fun x -> x <> '.') "" + |> equal "" + + #if FABLE_COMPILER + testCase "System.Environment.NewLine works" <| fun () -> + System.Environment.NewLine + |> equal "\n" + #endif + + testCase "System.Uri.UnescapeDataString works" <| fun () -> System.Uri.UnescapeDataString("Kevin%20van%20Zonneveld%21") |> equal "Kevin van Zonneveld!" System.Uri.UnescapeDataString("http%3A%2F%2Fkvz.io%2F") |> equal "http://kvz.io/" System.Uri.UnescapeDataString("http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a") |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" - testCase "System.Uri.EscapeDataString works" <| fun () -> + testCase "System.Uri.EscapeDataString works" <| fun () -> System.Uri.EscapeDataString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld%21" System.Uri.EscapeDataString("http://kvz.io/") |> equal "http%3A%2F%2Fkvz.io%2F" System.Uri.EscapeDataString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") |> equal "http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3DLocutus%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a" - testCase "System.Uri.EscapeUriString works" <| fun () -> + testCase "System.Uri.EscapeUriString works" <| fun () -> System.Uri.EscapeUriString("Kevin van Zonneveld!") |> equal "Kevin%20van%20Zonneveld!" System.Uri.EscapeUriString("http://kvz.io/") |> equal "http://kvz.io/" System.Uri.EscapeUriString("http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a") |> equal "http://www.google.nl/search?q=Locutus&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a" - // See #1628, though I'm not sure if the compiled tests are passing just the function reference without wrapping it - testCase "Passing Char.IsDigit as a function reference doesn't make String.filter hang" <| fun () -> - "Hello! 123" |> String.filter System.Char.IsDigit |> equal "123" - - testCase "sprintf with double % should be unescaped" <| fun () -> - sprintf "%d%%" 100 |> equal "100%" - - testCase "interpolated string with double % should be unescaped" <| fun () -> - $"{100}%%" |> equal "100%" - - testCase "interpolated string with format and double % should be unescaped" <| fun () -> - $"%.2f{100.4566666}%%" |> equal "100.46%" - - testCase "interpolated string with double % should not interfere with holes afterwards " <| fun () -> - $"%%{99. - 1.5}".Replace(",", ".") |> equal "%97.5" - - testCase "interpolated string with double braces should be unescaped" <| fun () -> - $"{{ {100} }}" |> equal "{ 100 }" - - testCase "interpolated string with format and double braces should be unescaped" <| fun () -> - $"{{ %.2f{100.4566666} }}" |> equal "{ 100.46 }" - - testCase "interpolated string with consecutive holes work" <| fun () -> - $"""{"foo"}{5}""" |> equal "foo5" - $"""%s{"foo"}%i{5}""" |> equal "foo5" - $"""{"foo"}/{5}.fsi""" |> equal "foo/5.fsi" - - testCase "Can create FormattableString" <| fun () -> - let orderAmount = 100 - let convert (s: FormattableString) = s - let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" - s.Format |> equal "You owe: {0:N5} {1} {2}" - s.ArgumentCount |> equal 3 - s.GetArgument(2) |> equal (box true) - s.GetArguments() |> equal [|100; 3; true|] - let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" - s2.Format |> equal "{0}This is \"{1}\" awesome!" - s2.GetArguments() |> equal [|box 7; box "really"|] - let s3: FormattableString = $"""I have no holes""" - s3.Format |> equal "I have no holes" - s3.GetArguments() |> equal [||] - let s4: FormattableString = $"I have `backticks`" - s4.Format |> equal "I have `backticks`" - let s5: FormattableString = $"I have {{escaped braces}} and %%percentage%%" - s5.Format |> equal "I have {{escaped braces}} and %percentage%" - () + // See #1628, though I'm not sure if the compiled tests are passing just the function reference without wrapping it + testCase "Passing Char.IsDigit as a function reference doesn't make String.filter hang" <| fun () -> + "Hello! 123" |> String.filter System.Char.IsDigit |> equal "123" + + testCase "sprintf with double % should be unescaped" <| fun () -> + sprintf "%d%%" 100 |> equal "100%" + + testCase "interpolated string with double % should be unescaped" <| fun () -> + $"{100}%%" |> equal "100%" + + testCase "interpolated string with format and double % should be unescaped" <| fun () -> + $"%.2f{100.4566666}%%" |> equal "100.46%" + + testCase "interpolated string with double % should not interfere with holes afterwards " <| fun () -> + $"%%{99. - 1.5}".Replace(",", ".") |> equal "%97.5" + + testCase "interpolated string with double braces should be unescaped" <| fun () -> + $"{{ {100} }}" |> equal "{ 100 }" + + testCase "interpolated string with format and double braces should be unescaped" <| fun () -> + $"{{ %.2f{100.4566666} }}" |> equal "{ 100.46 }" + + testCase "interpolated string with consecutive holes work" <| fun () -> + $"""{"foo"}{5}""" |> equal "foo5" + $"""%s{"foo"}%i{5}""" |> equal "foo5" + $"""{"foo"}/{5}.fsi""" |> equal "foo/5.fsi" + + testCase "Can create FormattableString" <| fun () -> + let orderAmount = 100 + let convert (s: FormattableString) = s + let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" + s.Format |> equal "You owe: {0:N5} {1} {2}" + s.ArgumentCount |> equal 3 + s.GetArgument(2) |> equal (box true) + s.GetArguments() |> equal [|100; 3; true|] + let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" + s2.Format |> equal "{0}This is \"{1}\" awesome!" + s2.GetArguments() |> equal [|box 7; box "really"|] + let s3: FormattableString = $"""I have no holes""" + s3.Format |> equal "I have no holes" + s3.GetArguments() |> equal [||] + let s4: FormattableString = $"I have `backticks`" + s4.Format |> equal "I have `backticks`" + let s5: FormattableString = $"I have {{escaped braces}} and %%percentage%%" + s5.Format |> equal "I have {{escaped braces}} and %percentage%" + () #if FABLE_COMPILER - testCase "Can use FormattableString.GetStrings() extension" <| fun () -> + testCase "Can use FormattableString.GetStrings() extension" <| fun () -> // TypeScript will complain because TemplateStringsArray is not equivalent to string[] #if FABLE_COMPILER_TYPESCRIPT - let toArray = Seq.toArray + let toArray = Seq.toArray #else - let toArray = id + let toArray = id #endif - let orderAmount = 100 - let convert (s: FormattableString) = s - let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" - s.GetStrings() |> toArray |> equal [|"You owe: "; " "; " "; ""|] - let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" - s2.GetStrings() |> toArray |> equal [|""; "This is \""; "\" awesome!"|] - let s3: FormattableString = $"""I have no holes""" - s3.GetStrings() |> toArray |> equal [|"I have no holes"|] + let orderAmount = 100 + let convert (s: FormattableString) = s + let s = convert $"You owe: {orderAmount:N5} {3} {5 = 5}" + s.GetStrings() |> toArray |> equal [|"You owe: "; " "; " "; ""|] + let s2: FormattableString = $"""{5 + 2}This is "{"really"}" awesome!""" + s2.GetStrings() |> toArray |> equal [|""; "This is \""; "\" awesome!"|] + let s3: FormattableString = $"""I have no holes""" + s3.GetStrings() |> toArray |> equal [|"I have no holes"|] #endif ] diff --git a/tests/Php/TestString.fs b/tests/Php/TestString.fs index e127e45eb2..308cdb14ae 100644 --- a/tests/Php/TestString.fs +++ b/tests/Php/TestString.fs @@ -81,10 +81,12 @@ let ``test string interpolation works with inline expressions`` () = [] let ``test string interpolation works with anonymous records`` () = let person = - {| Name = "John" - Surname = "Doe" - Age = 32 - Country = "The United Kingdom" |} + {| + Name = "John" + Surname = "Doe" + Age = 32 + Country = "The United Kingdom" + |} $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" @@ -166,7 +168,7 @@ let ``test StringBuilder works`` () = sb.ToString() |> equal expected [] -let ``test StringBuilder.Lengh works`` () = +let ``test StringBuilder.Length works`` () = let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore // We don't test the AppendLine for Length because depending on the OS @@ -183,21 +185,69 @@ let ``test StringBuilder.ToString works with index and length`` () = [] let ``test StringBuilder.Clear works`` () = - let builder = new System.Text.StringBuilder() - builder.Append("1111") |> ignore - builder.Clear() |> ignore - equal "" (builder.ToString()) + let sb = new System.Text.StringBuilder() + sb.Append("1111") |> ignore + sb.Clear() |> ignore + equal "" (sb.ToString()) [] let ``test StringBuilder.Append works with various overloads`` () = - let builder = Text.StringBuilder() - .Append(Text.StringBuilder "aaa") - .Append("bcd".ToCharArray()) - .Append('/') - .Append(true) - .Append(5.2) - .Append(34) - equal "aaabcd/true5.234" (builder.ToString().ToLower()) + let sb = System.Text.StringBuilder() + .Append(System.Text.StringBuilder "aaa") + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .Append(5.2) + .Append(34) + equal "aaabcd/true5.234" (sb.ToString().ToLower()) + +[] +let ``StringBuilder.AppendFormat works`` () = + let sb = System.Text.StringBuilder() + sb.AppendFormat("Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + +[] +let ``StringBuilder.AppendFormat with provider works`` () = + let sb = System.Text.StringBuilder() + sb.AppendFormat(CultureInfo.InvariantCulture, "Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + +[] +let ``StringBuilder.Chars works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(1) |> equal 'b' + +[] +let ``StringBuilder.Chars throws when index is out of bounds`` () = + throwsAnyError <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(-1) |> ignore + sb.Chars(3) |> ignore + +[] +let ``StringBuilder.Replace works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + .Append("abc") + .Replace('a', 'x') + .Replace("cx", "yz") + sb.ToString() |> equal "xbyzbc" + +[] +let ``StringBuilder index getter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] |> equal 'b' + +[] +let ``StringBuilder index setter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] <- 'x' + sb.ToString() |> equal "axc" [] let ``test Conversion char to int works`` () = diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index 2630bd6aa6..1f41832700 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -3,10 +3,14 @@ module Fable.Tests.String open System open System.Globalization +open Util open Util.Testing #nowarn "44" // This construct is deprecated. Uri.EscapeUriString can corrupt the Uri string in some cases. (code 44) +// module M = +// let f x = nameof x + [] let formatCoordinateBody = "(%f,%f)" @@ -24,6 +28,12 @@ let containsInOrder (substrings: string list) (str: string) = lastIndex <- i success) +// [] +// let ``F# nameof works`` () = +// M.f 12 |> equal "x" +// nameof M |> equal "M" +// nameof M.f |> equal "f" + [] let ``test sprintf works`` () = // Immediately applied @@ -102,10 +112,12 @@ let ``test string interpolation works with inline expressions`` () = [] let ``test string interpolation works with anonymous records`` () = let person = - {| Name = "John" - Surname = "Doe" - Age = 32 - Country = "The United Kingdom" |} + {| + Name = "John" + Surname = "Doe" + Age = 32 + Country = "The United Kingdom" + |} $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" @@ -201,38 +213,87 @@ let ``test StringBuilder works`` () = sb.ToString() |> equal expected [] -let ``test StringBuilder.Lengh works`` () = +let ``test StringBuilder.Length works`` () = let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore // We don't test the AppendLine for Length because depending on the OS // the result is different. Unix \n VS Windows \r\n // sb.AppendLine() |> ignore - equal 5 sb.Length + sb.Length |> equal 5 [] let ``test StringBuilder.ToString works with index and length`` () = let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore sb.AppendLine() |> ignore - equal "ll" (sb.ToString(2, 2)) + sb.ToString(2, 2) |> equal "ll" [] let ``test StringBuilder.Clear works`` () = - let builder = new System.Text.StringBuilder() - builder.Append("1111") |> ignore - builder.Clear() |> ignore - equal "" (builder.ToString()) + let sb = new System.Text.StringBuilder() + sb.Append("1111") |> ignore + sb.Clear() |> ignore + sb.ToString() |> equal "" [] let ``test StringBuilder.Append works with various overloads`` () = - let builder = Text.StringBuilder() - .Append(Text.StringBuilder "aaa") - .Append("bcd".ToCharArray()) - .Append('/') - .Append(true) - .AppendFormat(CultureInfo.InvariantCulture, "{0}", 5.2) - .Append(34) - equal "aaabcd/true5.234" (builder.ToString().ToLower()) + let sb = System.Text.StringBuilder() + .Append(System.Text.StringBuilder "aaa") + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .AppendFormat(CultureInfo.InvariantCulture, "{0}", 5.2) + .Append(34) + let actual = sb.ToString().ToLower() + actual |> equal "aaabcd/true5.234" + +[] +let ``test StringBuilder.AppendFormat works`` () = + let sb = System.Text.StringBuilder() + sb.AppendFormat("Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + +[] +let ``test StringBuilder.AppendFormat with provider works`` () = + let sb = System.Text.StringBuilder() + sb.AppendFormat(CultureInfo.InvariantCulture, "Hello{0}World{1}", " ", "!") |> ignore + sb.ToString() |> equal "Hello World!" + +[] +let ``test StringBuilder.Chars works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(1) |> equal 'b' + +[] +let ``test StringBuilder.Chars throws when index is out of bounds`` () = + throwsAnyError <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(-1) |> ignore + sb.Chars(3) |> ignore + +[] +let ``test StringBuilder.Replace works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + .Append("abc") + .Replace('a', 'x') + .Replace("cx", "yz") + equal "xbyzbc" (sb.ToString()) + +[] +let ``test StringBuilder index getter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + equal 'b' (sb[1]) + +[] +let ``test StringBuilder index setter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] <- 'x' + equal "axc" (sb.ToString()) [] let ``test Conversion char to int works`` () = diff --git a/tests/Rust/tests/src/StringTests.fs b/tests/Rust/tests/src/StringTests.fs index 5334091997..d270f4e889 100644 --- a/tests/Rust/tests/src/StringTests.fs +++ b/tests/Rust/tests/src/StringTests.fs @@ -40,6 +40,12 @@ let containsInOrder (substrings: string list) (str: string) = lastIndex <- i success) +[] +let ``F# nameof works`` () = + M.f 12 |> equal "x" + nameof M |> equal "M" + nameof M.f |> equal "f" + [] let ``Adding strings works`` () = let a = "hello" @@ -70,7 +76,7 @@ let ``String literal addition is optimized`` () = [] let ``StringBuilder works`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore sb.AppendLine() |> ignore sb.AppendLine("World!") |> ignore @@ -79,52 +85,87 @@ let ``StringBuilder works`` () = [] let ``StringBuilder.Length works`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore // We don't test the AppendLine for Length because depending on the OS // the result is different. Unix \n VS Windows \r\n // sb.AppendLine() |> ignore - equal 5 sb.Length + sb.Length |> equal 5 [] let ``StringBuilder.ToString works with index and length`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() sb.Append("Hello") |> ignore sb.AppendLine() |> ignore - equal "ll" (sb.ToString(2, 2)) + sb.ToString(2, 2) |> equal "ll" [] let ``StringBuilder.Clear works`` () = - let builder = new Text.StringBuilder() - builder.Append("1111") |> ignore - builder.Clear() |> ignore - equal "" (builder.ToString()) + let sb = new System.Text.StringBuilder() + sb.Append("1111") |> ignore + sb.Clear() |> ignore + sb.ToString() |> equal "" [] let ``StringBuilder.Append works with various overloads`` () = - let sb = - Text.StringBuilder() - .Append(Text.StringBuilder("aaa")) - .Append("bcd".ToCharArray()) - .Append('/') - .Append(true) - .Append(5.2) - .Append(34) + let sb = System.Text.StringBuilder() + .Append(System.Text.StringBuilder("aaa")) + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .Append(5.2) + .Append(34) let actual = sb.ToString().Replace(",", ".").ToLower() actual |> equal "aaabcd/true5.234" [] let ``StringBuilder.AppendFormat works`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() sb.AppendFormat("Hello{0}World{1}", " ", "!") |> ignore sb.ToString() |> equal "Hello World!" [] let ``StringBuilder.AppendFormat with provider works`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() sb.AppendFormat(CultureInfo.InvariantCulture, "Hello{0}World{1}", " ", "!") |> ignore sb.ToString() |> equal "Hello World!" +[] +let ``StringBuilder.Chars works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(1) |> equal 'b' + +[] +let ``StringBuilder.Chars throws when index is out of bounds`` () = + throwsAnyError <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") + sb.Chars(-1) |> ignore + sb.Chars(3) |> ignore + +[] +let ``StringBuilder.Replace works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + .Append("abc") + .Replace('a', 'x') + .Replace("cx", "yz") + sb.ToString() |> equal "xbyzbc" + +[] +let ``StringBuilder index getter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] |> equal 'b' + +[] +let ``StringBuilder index setter works`` () = + let sb = System.Text.StringBuilder() + .Append("abc") + sb[1] <- 'x' + sb.ToString() |> equal "axc" + [] let ``kprintf works`` () = let f (s:string) = s + "XX" @@ -148,7 +189,7 @@ let ``ksprintf works`` () = [] let ``kbprintf works`` () = - let sb = Text.StringBuilder() + let sb = System.Text.StringBuilder() let mutable i = 0 let f () = i <- i + 1 Printf.kbprintf f sb "Hello" @@ -166,7 +207,7 @@ let ``ksprintf curries correctly`` () = [] let ``bprintf works`` () = - let sb = Text.StringBuilder(10) + let sb = System.Text.StringBuilder(10) Printf.bprintf sb "Hello" Printf.bprintf sb " %s!" "world" sb.ToString() |> equal "Hello world!" @@ -280,12 +321,6 @@ let ``sprintf \"%A\" with overloaded string works`` () = // sprintf "%A" o |> ignore // #endif -[] -let ``F# nameof works`` () = - M.f 12 |> equal "x" - nameof M |> equal "M" - nameof M.f |> equal "f" - [] let ``string interpolation works`` () = let name = "Phillip" @@ -438,7 +473,7 @@ let ``sprintf integers with sign and padding works`` () = // See #1931 // sprintf "%- 4i" 5 |> equal " 5 " //TODO: [] -let ``test format string can use and compose string literals`` = +let ``format string can use and compose string literals`` = let renderedCoordinates = sprintf formatCoordinateBody 0.25 0.75 let renderedText = sprintf fullFormat 0.25 0.75