Currency mask support
commit_hash:c3ec8930a82a0af840bcf9bf76dcf2033fa1c1df
@@ -16192,6 +16192,7 @@
|
||||
"client/ios/LayoutKit/LayoutKit/Base/AttributedStringExtensions.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/AttributedStringExtensions.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/CMTimeExtensions.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/CMTimeExtensions.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/MaskedInputViewModel.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/MaskedInputViewModel.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/Masks/CurrencyMaskFormatter.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/Masks/CurrencyMaskFormatter.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/Masks/FixedLengthMaskFormatter.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/Masks/FixedLengthMaskFormatter.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/Masks/MaskValidator.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/Masks/MaskValidator.swift",
|
||||
"client/ios/LayoutKit/LayoutKit/Base/Masks/PhoneMaskFormatter.swift":"divkit/public/client/ios/LayoutKit/LayoutKit/Base/Masks/PhoneMaskFormatter.swift",
|
||||
@@ -16510,6 +16511,7 @@
|
||||
"client/ios/LayoutKit/LayoutKitTests/UI/Views/TextSelectionTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/UI/Views/TextSelectionTests.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/UIElementPathTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/UIElementPathTests.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/Utils.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/Utils.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/ViewModels/CurrencyMaskFormatterTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/ViewModels/CurrencyMaskFormatterTests.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/ViewModels/FixedLengthMaskFormatterTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/ViewModels/FixedLengthMaskFormatterTests.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/ViewModels/GalleryViewLayoutTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/ViewModels/GalleryViewLayoutTests.swift",
|
||||
"client/ios/LayoutKit/LayoutKitTests/ViewModels/GalleryViewMetricsTests.swift":"divkit/public/client/ios/LayoutKit/LayoutKitTests/ViewModels/GalleryViewMetricsTests.swift",
|
||||
@@ -17689,6 +17691,14 @@
|
||||
"client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step5.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step5.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step6.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step6.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step7.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/base-properties_414@3x_step7.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step0.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step0.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step1.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step1.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step2.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step2.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step3.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_375@2x_step3.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step0.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step0.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step1.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step1.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step2.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step2.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step3.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/currency_input_mask_414@3x_step3.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step0.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step0.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step1.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step1.png",
|
||||
"client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step10.png":"divkit/public/client/ios/Tests/reference_snapshots/div-input/fixed_length_input_mask_375@2x_step10.png",
|
||||
|
||||
@@ -283,6 +283,7 @@
|
||||
8CF32B7E2875E12F003F799A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CF32B7D2875E12F003F799A /* Utils.swift */; };
|
||||
8CF91B042D0AE4ED0082FDC1 /* MockDivActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CF91B032D0AE4ED0082FDC1 /* MockDivActionHandler.swift */; };
|
||||
8CFD4C9E2B2C5B780092814C /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFD4C9D2B2C5B780092814C /* Theme.swift */; };
|
||||
9807E2E32F17D24B00633FB7 /* CurrencyMaskFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9807E2E22F17D23200633FB7 /* CurrencyMaskFormatterTests.swift */; };
|
||||
982A7F142C9C08F30081DE74 /* PagerViewLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982A7F132C9C08F30081DE74 /* PagerViewLayoutTests.swift */; };
|
||||
983406132E85C3A4004B1AA3 /* DivTemplatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983406122E85C39C004B1AA3 /* DivTemplatesTests.swift */; };
|
||||
9860E7E72D9C326E00F93B48 /* TextInputBlockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9860E7E62D9C325B00F93B48 /* TextInputBlockTests.swift */; };
|
||||
@@ -675,6 +676,7 @@
|
||||
8CF8BBE92B32682700569777 /* DivBuilders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DivBuilders.swift; sourceTree = "<group>"; };
|
||||
8CF91B032D0AE4ED0082FDC1 /* MockDivActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDivActionHandler.swift; sourceTree = "<group>"; };
|
||||
8CFD4C9D2B2C5B780092814C /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
9807E2E22F17D23200633FB7 /* CurrencyMaskFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyMaskFormatterTests.swift; sourceTree = "<group>"; };
|
||||
982A7F132C9C08F30081DE74 /* PagerViewLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerViewLayoutTests.swift; sourceTree = "<group>"; };
|
||||
983406122E85C39C004B1AA3 /* DivTemplatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DivTemplatesTests.swift; sourceTree = "<group>"; };
|
||||
9860E7E62D9C325B00F93B48 /* TextInputBlockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputBlockTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1410,6 +1412,7 @@
|
||||
437B19D329C8A73300A4C467 /* MaskValidatorTests.swift */,
|
||||
65509D262A98EA4F00F75699 /* FixedLengthMaskFormatterTests.swift */,
|
||||
65509D282A98EA9200F75699 /* PhoneMaskFormatterTests.swift */,
|
||||
9807E2E22F17D23200633FB7 /* CurrencyMaskFormatterTests.swift */,
|
||||
);
|
||||
name = ViewModels;
|
||||
path = LayoutKit/LayoutKitTests/ViewModels;
|
||||
@@ -2236,6 +2239,7 @@
|
||||
65509D272A98EA4F00F75699 /* FixedLengthMaskFormatterTests.swift in Sources */,
|
||||
8C97EA3C2A5C169E00EC0C3E /* UIViewRenderable+AccessibilityTests.swift in Sources */,
|
||||
431DC9C22961EF84007FA268 /* ContainerBlockLayoutTests.swift in Sources */,
|
||||
9807E2E32F17D24B00633FB7 /* CurrencyMaskFormatterTests.swift in Sources */,
|
||||
8CF329572875D5C8003F799A /* BlockTests+Layout.swift in Sources */,
|
||||
8CF3294B2875D5C8003F799A /* GenericCollectionLayoutTests.swift in Sources */,
|
||||
8CF3294F2875D5C8003F799A /* VisibilityActionPerformersTests.swift in Sources */,
|
||||
|
||||
@@ -293,8 +293,12 @@ extension DivInputMask {
|
||||
patternElements: divFixedLengthInputMask.patternElements
|
||||
.map { $0.makePatternElement(resolver) }
|
||||
))
|
||||
case .divCurrencyInputMask:
|
||||
nil
|
||||
case let .divCurrencyInputMask(divCurrencyInputMask):
|
||||
MaskValidator(
|
||||
formatter: CurrencyMaskFormatter(
|
||||
locale: divCurrencyInputMask.resolveLocale(resolver)
|
||||
)
|
||||
)
|
||||
case .divPhoneInputMask:
|
||||
MaskValidator(formatter: PhoneMaskFormatter(
|
||||
masksByCountryCode: PhoneMasks().value.typedJSON(),
|
||||
@@ -310,8 +314,11 @@ extension DivInputMask {
|
||||
variableName: divFixedLengthInputMask.rawTextVariable,
|
||||
defaultValue: ""
|
||||
)
|
||||
case .divCurrencyInputMask:
|
||||
nil
|
||||
case let .divCurrencyInputMask(divCurrencyInputMask):
|
||||
context.makeBinding(
|
||||
variableName: divCurrencyInputMask.rawTextVariable,
|
||||
defaultValue: ""
|
||||
)
|
||||
case let .divPhoneInputMask(divPhoneInputMask):
|
||||
context.makeBinding(
|
||||
variableName: divPhoneInputMask.rawTextVariable,
|
||||
|
||||
@@ -83,6 +83,12 @@ public class MaskedInputViewModel {
|
||||
string: string
|
||||
)
|
||||
}
|
||||
|
||||
let newInputData = self.maskValidator.formatted(rawText: newString)
|
||||
if newInputData.rawText == self.rawText {
|
||||
return // Changes rejected
|
||||
}
|
||||
|
||||
self.rawText = self.maskValidator.formatted(rawText: newString).rawText
|
||||
self.rawCursorPosition = newCursorPosition
|
||||
}.dispose(in: disposePool)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import Foundation
|
||||
|
||||
public final class CurrencyMaskFormatter: MaskFormatter {
|
||||
private let locale: Locale
|
||||
private var lastCorrectInput = InputData(
|
||||
text: "", cursorPosition: nil, rawData: []
|
||||
)
|
||||
|
||||
private lazy var formatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = locale
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.maximumFractionDigits = 2
|
||||
return formatter
|
||||
}()
|
||||
|
||||
public init(locale: String?) {
|
||||
self.locale = locale.map { Locale(identifier: $0) } ?? Locale.current
|
||||
}
|
||||
|
||||
public func formatted(
|
||||
rawText: String,
|
||||
rawCursorPosition: CursorData?
|
||||
) -> InputData {
|
||||
guard !rawText.isEmpty else {
|
||||
return InputData(text: "", cursorPosition: nil, rawData: [])
|
||||
}
|
||||
|
||||
guard let formattedText: String = {
|
||||
let pattern = "^(?:0|[1-9]\\d*)(?:[.,]\\d{0,2})?$"
|
||||
|
||||
if rawText.range(of: pattern, options: .regularExpression) != nil,
|
||||
let val = formattedValue(text: rawText) {
|
||||
return val
|
||||
}
|
||||
|
||||
return nil
|
||||
}() else {
|
||||
return lastCorrectInput
|
||||
}
|
||||
|
||||
var newCursorPosition: CursorPosition? = if rawText.endIndex == rawCursorPosition?
|
||||
.cursorPosition.rawValue {
|
||||
CursorPosition(rawValue: formattedText.endIndex)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
var rawData: [InputData.RawCharacter] = []
|
||||
var formattedTextPointer = 0
|
||||
|
||||
for rawTextPointer in 0..<rawText.count {
|
||||
while formattedTextPointer < formattedText.count {
|
||||
guard let formattedTextChar = formattedText[formattedTextPointer],
|
||||
let rawTextChar = rawText[rawTextPointer] else {
|
||||
break
|
||||
}
|
||||
|
||||
formattedTextPointer += 1
|
||||
if newCursorPosition == nil,
|
||||
formattedTextChar.char == rawTextChar.char,
|
||||
rawTextChar.index == rawCursorPosition?.cursorPosition.rawValue {
|
||||
newCursorPosition = CursorPosition(
|
||||
rawValue: formattedTextChar.index
|
||||
)
|
||||
}
|
||||
|
||||
if formattedTextChar.char == rawTextChar.char {
|
||||
rawData.append(
|
||||
InputData.RawCharacter(
|
||||
char: rawTextChar.char,
|
||||
index: formattedTextChar.index
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let resultInputData = InputData(
|
||||
text: formattedText,
|
||||
cursorPosition: newCursorPosition,
|
||||
rawData: rawData
|
||||
)
|
||||
|
||||
lastCorrectInput = resultInputData
|
||||
|
||||
return resultInputData
|
||||
}
|
||||
|
||||
public func equals(_ other: any MaskFormatter) -> Bool {
|
||||
guard let other = other as? CurrencyMaskFormatter else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self.locale == other.locale
|
||||
}
|
||||
|
||||
private func formattedValue(text: String) -> String? {
|
||||
let decimalSeparator = locale.decimalSeparator ?? "."
|
||||
|
||||
formatter.minimumFractionDigits = 0
|
||||
if let value = Int(text),
|
||||
let formatted = formatter.string(from: NSNumber(value: value)) {
|
||||
return formatted
|
||||
}
|
||||
|
||||
if let value = Int(text.trimTrailingDecimalSeparator(decimalSeparator)),
|
||||
let formatted = formatter.string(from: NSNumber(value: value)) {
|
||||
return formatted + decimalSeparator
|
||||
}
|
||||
|
||||
formatter.minimumFractionDigits = text.split(separator: decimalSeparator.first ?? ".").last?
|
||||
.count ?? 0
|
||||
|
||||
if let value = Decimal(string: text, locale: locale) {
|
||||
return formatter.string(from: value as NSNumber)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
fileprivate subscript(_ i: Int) -> InputData.RawCharacter? {
|
||||
guard i >= 0, i < count else { return nil }
|
||||
let index = self.index(startIndex, offsetBy: i)
|
||||
return InputData.RawCharacter(
|
||||
char: self[index],
|
||||
index: index
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func trimTrailingDecimalSeparator(_ separator: String) -> String {
|
||||
if hasSuffix(separator) {
|
||||
return String(dropLast())
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public final class MaskValidator: Equatable {
|
||||
}
|
||||
|
||||
public func formatted(rawText: String, rawCursorPosition: CursorData? = nil) -> InputData {
|
||||
formatter.formatted(rawText: rawText, rawCursorPosition: rawCursorPosition)
|
||||
self.formatter.formatted(rawText: rawText, rawCursorPosition: rawCursorPosition)
|
||||
}
|
||||
|
||||
public func removeSymbols(at index: String.Index, data: InputData) -> (String, CursorData?) {
|
||||
@@ -88,7 +88,7 @@ public final class MaskValidator: Equatable {
|
||||
public enum CursorPositionTag {}
|
||||
public typealias CursorPosition = Tagged<CursorPositionTag, String.Index>
|
||||
|
||||
public struct CursorData: Equatable {
|
||||
public struct CursorData: Equatable, Hashable {
|
||||
let cursorPosition: CursorPosition
|
||||
let afterNonDecodingSymbols: Bool
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
@testable import LayoutKit
|
||||
import Testing
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
@Suite
|
||||
struct CurrencyMaskFormatterTests {
|
||||
@Test(
|
||||
arguments: [
|
||||
("", "", ""),
|
||||
("1", "1", "1"),
|
||||
("12", "12", "12"),
|
||||
("1234", "1,234", "1234"),
|
||||
("1234.0", "1,234.0", "1234.0"),
|
||||
("1234.06", "1,234.06", "1234.06"),
|
||||
("1234.", "1,234.", "1234."),
|
||||
("0.33", "0.33", "0.33"),
|
||||
]
|
||||
)
|
||||
func rawTextFormattingUsLocal(
|
||||
rawText: String, expectedText: String, expectedRawText: String
|
||||
) {
|
||||
let formatter = makeFormatter()
|
||||
let result = formatter.formatted(rawText: rawText, rawCursorPosition: nil)
|
||||
|
||||
#expect(result.text == expectedText)
|
||||
#expect(result.rawText == expectedRawText)
|
||||
}
|
||||
|
||||
@Test(
|
||||
arguments: [
|
||||
"e4",
|
||||
"053",
|
||||
".95",
|
||||
"345.0543",
|
||||
"345.05.43"
|
||||
]
|
||||
)
|
||||
func rawTextWrongFormattingUsLocal(
|
||||
rawText: String
|
||||
) {
|
||||
let formatter = makeFormatter()
|
||||
|
||||
let result = formatter.formatted(rawText: rawText, rawCursorPosition: nil)
|
||||
|
||||
#expect(result.rawText == "")
|
||||
}
|
||||
|
||||
@Test(
|
||||
arguments: [
|
||||
("", "", ""),
|
||||
("1", "1", "1"),
|
||||
("12", "12", "12"),
|
||||
("1234", "1 234", "1234"),
|
||||
("1234,0", "1 234,0", "1234,0"),
|
||||
("1234,06", "1 234,06", "1234,06"),
|
||||
("1234,", "1 234,", "1234,"),
|
||||
("0,33", "0,33", "0,33")
|
||||
]
|
||||
)
|
||||
func rawTextFormattingRusLocal(
|
||||
rawText: String, expectedText: String, expectedRawText: String
|
||||
) {
|
||||
let formatter = makeFormatter(localIdentifier: "ru_RU")
|
||||
let result = formatter.formatted(rawText: rawText, rawCursorPosition: nil)
|
||||
|
||||
#expect(result.text == expectedText)
|
||||
#expect(result.rawText == expectedRawText)
|
||||
}
|
||||
|
||||
@Test
|
||||
func invalidInputReturnsLastCorrectValue() {
|
||||
let formatter = makeFormatter()
|
||||
|
||||
let first = formatter.formatted(rawText: "1234", rawCursorPosition: nil)
|
||||
#expect(first.text == "1,234")
|
||||
|
||||
let second = formatter.formatted(rawText: "12aa34", rawCursorPosition: nil)
|
||||
|
||||
#expect(second.text == "1,234")
|
||||
#expect(second.rawText == "1234")
|
||||
}
|
||||
|
||||
@Test
|
||||
func rawData() {
|
||||
let formatter = makeFormatter()
|
||||
let rawText = "1234.5"
|
||||
let text = "1 234.5"
|
||||
|
||||
let data = formatter.formatted(rawText: rawText, rawCursorPosition: nil).rawData
|
||||
|
||||
let expected: [InputData.RawCharacter] = [
|
||||
.init(char: "1", index: text.index(text.startIndex, offsetBy: 0)),
|
||||
.init(char: "2", index: text.index(text.startIndex, offsetBy: 2)),
|
||||
.init(char: "3", index: text.index(text.startIndex, offsetBy: 3)),
|
||||
.init(char: "4", index: text.index(text.startIndex, offsetBy: 4)),
|
||||
.init(char: ".", index: text.index(text.startIndex, offsetBy: 5)),
|
||||
.init(char: "5", index: text.index(text.startIndex, offsetBy: 6)),
|
||||
]
|
||||
|
||||
#expect(data == expected)
|
||||
}
|
||||
|
||||
@Test(
|
||||
arguments: [
|
||||
(0, 0),
|
||||
(1, 2),
|
||||
(3, 4),
|
||||
(5, 7)
|
||||
]
|
||||
)
|
||||
func cursorPositionWithoutFormattingSymbols(
|
||||
rawCursor: Int, expectedCursor: Int
|
||||
) {
|
||||
let formatter = makeFormatter()
|
||||
let rawText = "1234567"
|
||||
|
||||
let result = formatter.formatted(
|
||||
rawText: rawText,
|
||||
rawCursorPosition: .init(rawCursor, false)
|
||||
)
|
||||
|
||||
let expected: CursorPosition = .init(integerLiteral: expectedCursor)
|
||||
|
||||
#expect(result.cursorPosition == expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
func cursorPositionAtEnd() {
|
||||
let formatter = makeFormatter()
|
||||
let rawText = "1234.56"
|
||||
|
||||
let result = formatter.formatted(
|
||||
rawText: rawText,
|
||||
rawCursorPosition: .init(rawText.count, false)
|
||||
)
|
||||
|
||||
#expect(result.cursorPosition?.rawValue == result.text.endIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
func cursorPositionAfterDecimalSeparator() {
|
||||
let formatter = makeFormatter()
|
||||
let rawText = "1234.5"
|
||||
|
||||
let result = formatter.formatted(
|
||||
rawText: rawText,
|
||||
rawCursorPosition: .init(5, false)
|
||||
)
|
||||
|
||||
#expect(result.cursorPosition == 6)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeFormatter(
|
||||
localIdentifier: String = "en_US"
|
||||
) -> CurrencyMaskFormatter {
|
||||
CurrencyMaskFormatter(locale: localIdentifier)
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
@@ -2,7 +2,8 @@
|
||||
"description": "Currency input mask",
|
||||
"platforms": [
|
||||
"android",
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
],
|
||||
"div_data": {
|
||||
"card": {
|
||||
|
||||
@@ -2731,7 +2731,8 @@
|
||||
"case_id": 106,
|
||||
"platforms": [
|
||||
"android",
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
],
|
||||
"tags": [
|
||||
"DivInput"
|
||||
|
||||