This test is OK, because I use toString explicitly
"have one sentence by '...'" in {
val text1 = Text("some text...")
text1.sentences must have size(1)
text1.sentences(0).toString must_== "some text"
}
If without toSting the test it fails with message like:
Expected :some text
Actual :some text
java.lang.Exception: 'some text: dictionary.Sentence' is not equal to 'some text: java.lang.String'
I understand the sense of it (in general), but since toString is
invoked anyway shouldn't it check string to string then?
What is the best way to write this test to be concise? Without using
toString directly.
I think you might be confusing toString and equals. When you say what you wanted to say:
text1.sentences(0) must_== "some text"
What you are really saying is:
text1.sentences(0).equals("some text") must beTrue
If you want this to work, then you would need there to be an equals function on the Sentence class that used the toString of the sentence to compare to the incoming object (a String in this case). A simple spec showing that could look like this:
class Sentence(text:String){
override def equals(obj:Any) = {
this.toString == obj.toString
}
override def toString = text
}
class EqualitySpec extends Specification{
"A sentence" should{
"be equal to plain text" in {
val sentence = new Sentence("hello world")
sentence must be_==("hello world")
}
}
}
Now this works great if that Sentence class is your own class. If it's in a third party library and you have no control over the equals function then you might be stuck with things the way that they are.
Related
I'm having trouble printing localized text conditionally. For example, this localizes properly:
if valueFromDb.isEmpty {
Text("value_is_empty") //localized text
} else {
Text(valueFromDb)
}
It prints some text in the user's language if valueFromDb is empty, or it prints valueFromDb as it is if it's not. However, when I try to use the ternary operator it doesn't work:
Text(valueFromDb.isEmpty ? "value_is_empty" : valueFromDb)
When valueFromDb is empty, it prints "value_is_empty" rather than actual localized text. I get an error (a random one higher up in the hierarchy thanks to SwiftUI) when trying to cast it as LocalizedStringKey.
Edit: To be clear, I know I can do this:
valueFromDb.isEmpty ? Text("value_is_empty") : Text(valueFromDb)
However, I want to put the ternary conditional inside the Text() brackets because I will do this for several views, and each one will have many modifiers, so the code will become quite bloated.
The problem is due to type inference. You have to declare myString to be of type LocalizedStringKey and then everything will work as expected.
When you declare:
#State var mySrtring: LocalizedStringKey = "whatever"
Then:
Text(myString.isEmpty ? "error_text_localized" : myString)
uses this initializer:
public init(_ key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil)
When you declare it like this:
#State var mySrtring: String = "whatever"
Then:
Text(myString.isEmpty ? "error_text_localized" : myString)
uses this initialiser:
public init(verbatim content: String)
You have to put your valueFromDb in quotations, then it should work fine.
Text(valueFromDb.isEmpty ? "value_is_empty" : "\(valueFromDb)")
I have a simple struct that I will build upon. Right now it has one field, an Int.
struct Card: CustomStringConvertible {
let value: Int
init(value: Int) {
self.value = value
}
var description: String {
return "\(String(value))"
}
}
If I do this, I get the Card to print it's value
let c = Card(value: 1)
print(c)
Now if I put an array of Cards in a CardController like this:
class CardController: ObservableObject {
#Published
var cards: [Card] = [
Card(value: 1),
Card(value: 2),
Card(value: 3)
]
Picker(selection: $selectedCardValue, label: Text("Choose a card")) {
ForEach(0..<cardController.cards.count) {
Text(self.cardController.cards[$0])
}
}
Text("You selected \(selectedCardValue)")
I'll get the error Initializer 'init(_:)' requires that 'Card' conform to StringProtocol. I'm not sure why I get this error. If I instead just change the cards to a type of [String] and values ["1", "2", "3"], the code works fine.
Any idea what's wrong here?
As E.Coms noted, the solution is to use one of the following:
Text(self.cardController.cards[$0].description)
Text(String(describing: self.cardController.cards[$0]))
Here's an explanation of why you have to do this inside the Text initializer, but not print().
Look at the two initializers for Text:
init(verbatim content: String) (docs)
init<S>(_ content: S) where S : StringProtocol (docs)
You must pass either a String or a Substring, the only two types conforming to StringProtocol. In this case, even though your type conforms to CustomStringConvertible, you are still passing a Card.
Contrast this with something like the print function:
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") (docs)
Notice that the print function's arguments are denoted by Any, which is explained as
Any can represent an instance of any type at all, including function types.
The print function then converts whatever you passed it to a String:
The textual representation for each item is the same as that obtained by calling String(item).
String has an initializer which takes a type conforming to CustomStringConvertible and returns the description property.
So the reason you can write print(Card()) and not Text(Card() is because the print function has an intermediate step through String that can understand your conformance to CustomStringConvertible, but Text does not. If Text allowed you to pass it any type, it would be both more ambiguous ("What is the text representation of this type?" is not necessarily immediately apparent, as it depends on a hierarchical set of protocols), and more work for the SwiftUI system, which is already doing a lot.
You may miss the description by chance.
ForEach(0..<cardController.cards.count) {
Text(self.cardController.cards[$0].description)
}
Ruby has this method called the block_given in it so that we can check to see if a block is passed and process accordingly if given. Is there an equivalent method in crystal?
Crystal does not have it for a moment. But you can have similar behavior using method overloading:
def foo
foo {}
end
def foo
yield
end
foo { }
foo
If you absolutely must know whether a block was given, you can do something like this:
def foo(block : (String ->)? = nil)
if block
block.call("Block given")
else
puts "No block given"
end
end
def foo(&block : String ->)
foo(block)
end
foo { |s| puts s }
foo
(For more information on Proc syntax, see https://crystal-lang.org/reference/syntax_and_semantics/type_grammar.html#proc)
So my short code snippet looks like the following:
Source.fromFile(fileName).getLines().foldLeft(List[CsvEntry]())((csvList, currentLine) =>
currentLine match {
case pattern(organisation,yearAndQuartal,medKF,trueOrFalse,name,money) => new CsvEntry(organisation,yearAndQuartal,medKF.toInt,trueOrFalse.toInt,name,money) :: csvList
case default => csvList
The "fileName" is only the Name of the file, but it doesn't matter for my question.
And my csvList is defined like this:
type csvList = List[CsvEntry]
val list: csvList = List()
my class look like this:
class CsvEntry(val organisation: String, val yearAndQuartal : String, val medKF:Int, val trueOrFalse: Int, val name: String, val money:String){
override def toString = s"$organisation, $yearAndQuartal, $medKF,$trueOrFalse, $name, $money"
So my question is, whenever I am loading a file and writing it to my csvList it works, but when I am loading another 2nd file the old content gets overwirtten.
How can I change it in order to not get overwirtten, so it should only add it to the preceding data ?
The call beginning Source.fromFile... returns a list that you should then combine with the next call.
For example:
List("filename1", "filename2").map(processFile).flatten
where processFile is:
def processFile(fileName: String) = {
Source.fromFile(fileName).getLines().foldLeft... all the code in question
}
Nothing here can possibly get "overwritten", since there's no mutable state. Your type csvList and csvList in your foldLeft call are two very different things: the former is the type, the latter is the parameter.
Each time your snippet is executed, it returns a list of your CSV objects of a particular file. As an immutable list.
What you want is:
Make a function out of your snippet: def readFile(name: String): List[CsvFile]
Call it on the two files and save results to vals
Concat the two lists: list1 ++ list2
I'm using jQuery and have retrieved a string containing all the classes of an element like this:
var classes = $('[class^="menu-icon-"]').attr('class');
The string prints something like this: "foo menu-icon-icon-name bar"
How can find out what the "icon-name" part in the string is, whatever it may be?
It will always be with a class formatted as "menu-icon-{icon-name-here}" among whatever other classes the element may contain.
This works in all cases-
var menuname = classes.substr(classes.indexOf("menu-icon") + 10).split(" ")[0];
lets say you have one div's classes as a string
var class = "foo menu-icon-icon-name bar"
if you are certain that there will always be a space after "" you should be able to do something like
var name = class.match(/menu-icon-(.*) /)[1];