spec/calculator_spec.rb in dentaku-3.3.4 vs spec/calculator_spec.rb in dentaku-3.4.0
- old
+ new
@@ -145,10 +145,11 @@
end
it 'stores nested hashes' do
calculator.store(a: {basket: {of: 'apples'}}, b: 2)
expect(calculator.evaluate!('a.basket.of')).to eq('apples')
+ expect(calculator.evaluate!('a.basket')).to eq(of: 'apples')
expect(calculator.evaluate!('b')).to eq(2)
end
it 'stores arrays' do
calculator.store(a: [1, 2, 3])
@@ -194,10 +195,19 @@
weekly_apple_budget: "apples * 7",
pear: "1"
)).to eq(pear: 1, weekly_apple_budget: 21, weekly_fruit_budget: 25)
end
+ it "prefers variables over values in memory if they have no dependencies" do
+ expect(with_memory.solve!(
+ weekly_fruit_budget: "weekly_apple_budget + pear * 4",
+ weekly_apple_budget: "apples * 7",
+ pear: "1",
+ apples: "4"
+ )).to eq(apples: 4, pear: 1, weekly_apple_budget: 28, weekly_fruit_budget: 32)
+ end
+
it "preserves hash keys" do
expect(calculator.solve!(
'meaning_of_life' => 'age + kids',
'age' => 40,
'kids' => 2
@@ -317,10 +327,14 @@
end
expect { calculator.evaluate!('a + b') }.to raise_error do |error|
expect(error.unbound_variables).to eq(['a', 'b'])
end
expect(calculator.evaluate(unbound)).to be_nil
+ end
+
+ it 'accepts a block for custom handling of unbound variables' do
+ unbound = 'foo * 1.5'
expect(calculator.evaluate(unbound) { :bar }).to eq(:bar)
expect(calculator.evaluate(unbound) { |e| e }).to eq(unbound)
end
it 'fails to evaluate incomplete statements' do
@@ -389,10 +403,11 @@
end
it 'supports date arithmetic' do
expect(calculator.evaluate!('2020-01-01 + 30').to_date).to eq(Time.local(2020, 1, 31).to_date)
expect(calculator.evaluate!('2020-01-01 - 1').to_date).to eq(Time.local(2019, 12, 31).to_date)
+ expect(calculator.evaluate!('2020-01-01 - 2019-12-31')).to eq(1)
expect(calculator.evaluate!('2020-01-01 + duration(1, day)').to_date).to eq(Time.local(2020, 1, 2).to_date)
expect(calculator.evaluate!('2020-01-01 - duration(1, day)').to_date).to eq(Time.local(2019, 12, 31).to_date)
expect(calculator.evaluate!('2020-01-01 + duration(30, days)').to_date).to eq(Time.local(2020, 1, 31).to_date)
expect(calculator.evaluate!('2020-01-01 + duration(1, month)').to_date).to eq(Time.local(2020, 2, 1).to_date)
expect(calculator.evaluate!('2020-01-01 - duration(1, month)').to_date).to eq(Time.local(2019, 12, 1).to_date)
@@ -431,10 +446,88 @@
expect(calculator.evaluate('if (-1 = -1, -1, 5)')).to eq(-1)
expect(calculator.evaluate('round(-1.23, 1)')).to eq(BigDecimal('-1.2'))
expect(calculator.evaluate('NOT(some_boolean) AND -1 > 3', some_boolean: true)).to be_falsey
end
+ describe "any" do
+ it "enumerates values and returns true if any evaluation is truthy" do
+ expect(calculator.evaluate!('any(xs, x, x > 3)', xs: [1, 2, 3, 4])).to be_truthy
+ expect(calculator.evaluate!('any(xs, x, x > 3)', xs: 3)).to be_falsy
+ expect(calculator.evaluate!('any({1,2,3,4}, x, x > 3)')).to be_truthy
+ expect(calculator.evaluate!('any({1,2,3,4}, x, x > 10)')).to be_falsy
+ expect(calculator.evaluate!('any(users, u, u.age > 33)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to be_truthy
+ expect(calculator.evaluate!('any(users, u, u.age < 18)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to be_falsy
+ end
+ end
+
+ describe "all" do
+ it "enumerates values and returns true if all evaluations are truthy" do
+ expect(calculator.evaluate!('all(xs, x, x > 3)', xs: [1, 2, 3, 4])).to be_falsy
+ expect(calculator.evaluate!('any(xs, x, x > 2)', xs: 3)).to be_truthy
+ expect(calculator.evaluate!('all({1,2,3,4}, x, x > 0)')).to be_truthy
+ expect(calculator.evaluate!('all({1,2,3,4}, x, x > 10)')).to be_falsy
+ expect(calculator.evaluate!('all(users, u, u.age > 33)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to be_falsy
+ expect(calculator.evaluate!('all(users, u, u.age < 50)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to be_truthy
+ end
+ end
+
+ describe "map" do
+ it "maps values" do
+ expect(calculator.evaluate!('map(xs, x, x * 2)', xs: [1, 2, 3, 4])).to eq([2, 4, 6, 8])
+ expect(calculator.evaluate!('map({1,2,3,4}, x, x * 2)')).to eq([2, 4, 6, 8])
+ expect(calculator.evaluate!('map(users, u, u.age)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to eq([44, 27])
+ expect(calculator.evaluate!('map(users, u, u.age)', users: [
+ {"name" => "Bob", "age" => 44},
+ {"name" => "Jane", "age" => 27}
+ ])).to eq([44, 27])
+ expect(calculator.evaluate!('map(users, u, u.name)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to eq(["Bob", "Jane"])
+ expect(calculator.evaluate!('map(users, u, u.name)', users: [
+ {"name" => "Bob", "age" => 44},
+ {"name" => "Jane", "age" => 27}
+ ])).to eq(["Bob", "Jane"])
+ end
+ end
+
+ describe "pluck" do
+ it "plucks values from array of hashes" do
+ expect(calculator.evaluate!('pluck(users, age)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to eq([44, 27])
+ expect(calculator.evaluate!('pluck(users, age)', users: [
+ {"name" => "Bob", "age" => 44},
+ {"name" => "Jane", "age" => 27}
+ ])).to eq([44, 27])
+ expect(calculator.evaluate!('pluck(users, name)', users: [
+ {name: "Bob", age: 44},
+ {name: "Jane", age: 27}
+ ])).to eq(["Bob", "Jane"])
+ expect(calculator.evaluate!('pluck(users, name)', users: [
+ {"name" => "Bob", "age" => 44},
+ {"name" => "Jane", "age" => 27}
+ ])).to eq(["Bob", "Jane"])
+ end
+ end
+
it 'evaluates functions with stored variables' do
calculator.store("multi_color" => true, "number_of_sheets" => 5000, "sheets_per_minute_black" => 2000, "sheets_per_minute_color" => 1000)
result = calculator.evaluate('number_of_sheets / if(multi_color, sheets_per_minute_color, sheets_per_minute_black)')
expect(result).to eq(5)
end
@@ -601,10 +694,11 @@
describe 'math functions' do
Math.methods(false).each do |method|
it method do
if Math.method(method).arity == 2
expect(calculator.evaluate("#{method}(x,y)", x: 1, y: '2')).to eq(Math.send(method, 1, 2))
+ expect { calculator.evaluate!("#{method}(x)", x: 1) }.to raise_error(Dentaku::ParseError)
else
expect(calculator.evaluate("#{method}(1)")).to eq(Math.send(method, 1))
end
end
end
@@ -700,8 +794,26 @@
it 'should allow optout of nested hash' do
expect do
without_nested_data.solve!('a.b.c')
end.to raise_error(Dentaku::UnboundVariableError)
+ end
+ end
+
+ describe 'identifier cache' do
+ it 'reduces call count by caching results of resolved identifiers' do
+ called = 0
+ calculator.store_formula("A1", "B1+B1+B1")
+ calculator.store_formula("B1", "C1+C1+C1+C1")
+ calculator.store_formula("C1", "D1")
+ calculator.store("D1", proc { called += 1; 1 })
+
+ expect {
+ Dentaku.enable_identifier_cache!
+ }.to change {
+ called = 0
+ calculator.evaluate("A1")
+ called
+ }.from(12).to(1)
end
end
end