smartdown 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.md +21 -0
- data/README.md +204 -0
- data/bin/smartdown +41 -0
- data/lib/smartdown/engine/errors.rb +5 -0
- data/lib/smartdown/engine/predicate_evaluator.rb +23 -0
- data/lib/smartdown/engine/state.rb +63 -0
- data/lib/smartdown/engine/transition.rb +65 -0
- data/lib/smartdown/engine.rb +33 -0
- data/lib/smartdown/model/element/markdown_heading.rb +7 -0
- data/lib/smartdown/model/element/markdown_paragraph.rb +7 -0
- data/lib/smartdown/model/element/multiple_choice.rb +7 -0
- data/lib/smartdown/model/element/start_button.rb +7 -0
- data/lib/smartdown/model/flow.rb +24 -0
- data/lib/smartdown/model/front_matter.rb +37 -0
- data/lib/smartdown/model/nested_rule.rb +5 -0
- data/lib/smartdown/model/next_node_rules.rb +5 -0
- data/lib/smartdown/model/node.rb +47 -0
- data/lib/smartdown/model/predicate/equality.rb +7 -0
- data/lib/smartdown/model/predicate/named.rb +7 -0
- data/lib/smartdown/model/predicate/set_membership.rb +7 -0
- data/lib/smartdown/model/rule.rb +5 -0
- data/lib/smartdown/parser/base.rb +35 -0
- data/lib/smartdown/parser/directory_input.rb +61 -0
- data/lib/smartdown/parser/element/front_matter.rb +17 -0
- data/lib/smartdown/parser/element/markdown_heading.rb +14 -0
- data/lib/smartdown/parser/element/markdown_paragraph.rb +19 -0
- data/lib/smartdown/parser/element/multiple_choice_question.rb +24 -0
- data/lib/smartdown/parser/element/start_button.rb +15 -0
- data/lib/smartdown/parser/flow_interpreter.rb +50 -0
- data/lib/smartdown/parser/node_interpreter.rb +29 -0
- data/lib/smartdown/parser/node_parser.rb +37 -0
- data/lib/smartdown/parser/node_transform.rb +83 -0
- data/lib/smartdown/parser/predicates.rb +36 -0
- data/lib/smartdown/parser/rules.rb +51 -0
- data/lib/smartdown/version.rb +3 -0
- data/lib/smartdown.rb +9 -0
- data/spec/acceptance/parsing_spec.rb +109 -0
- data/spec/acceptance/smartdown_cli_spec.rb +16 -0
- data/spec/engine/predicate_evaluator_spec.rb +98 -0
- data/spec/engine/state_spec.rb +106 -0
- data/spec/engine/transition_spec.rb +150 -0
- data/spec/engine_spec.rb +79 -0
- data/spec/fixtures/acceptance/cover-sheet/cover-sheet.txt +14 -0
- data/spec/fixtures/acceptance/one-question/one-question.txt +3 -0
- data/spec/fixtures/acceptance/one-question/questions/q1.txt +9 -0
- data/spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt +3 -0
- data/spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt +3 -0
- data/spec/fixtures/acceptance/question-and-outcome/questions/q1.txt +9 -0
- data/spec/fixtures/directory_input/cover-sheet.txt +1 -0
- data/spec/fixtures/directory_input/outcomes/o1.txt +1 -0
- data/spec/fixtures/directory_input/questions/q1.txt +1 -0
- data/spec/fixtures/directory_input/scenarios/s1.txt +1 -0
- data/spec/fixtures/example.sd +17 -0
- data/spec/model/flow_spec.rb +42 -0
- data/spec/model/node_spec.rb +32 -0
- data/spec/parser/base_spec.rb +49 -0
- data/spec/parser/directory_input_spec.rb +56 -0
- data/spec/parser/element/front_matter_spec.rb +21 -0
- data/spec/parser/element/markdown_heading_spec.rb +24 -0
- data/spec/parser/element/markdown_paragraph_spec.rb +28 -0
- data/spec/parser/element/multiple_choice_question_spec.rb +31 -0
- data/spec/parser/element/start_button_parser_spec.rb +30 -0
- data/spec/parser/integration/cover_sheet_spec.rb +30 -0
- data/spec/parser/node_parser_spec.rb +133 -0
- data/spec/parser/predicates_spec.rb +65 -0
- data/spec/parser/rules_spec.rb +244 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/model_builder.rb +83 -0
- data/spec/support_specs/model_builder_spec.rb +90 -0
- metadata +218 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'smartdown/engine/state'
|
2
|
+
|
3
|
+
describe Smartdown::Engine::State do
|
4
|
+
subject {
|
5
|
+
described_class.new(current_node: :start_state)
|
6
|
+
}
|
7
|
+
|
8
|
+
it "raises if current_node not given to constructor" do
|
9
|
+
expect { described_class.new() }.to raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "initializes path and responses" do
|
13
|
+
expect(subject.get(:responses)).to eq []
|
14
|
+
expect(subject.get(:path)).to eq []
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#get" do
|
18
|
+
it "raises if a value is undefined" do
|
19
|
+
expect { subject.get(:a) }.to raise_error(Smartdown::Engine::UndefinedValue)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "by string or symbol" do
|
23
|
+
expect(subject.get(:current_node)).to eq(:start_state)
|
24
|
+
expect(subject.get("current_node")).to eq(:start_state)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#keys" do
|
29
|
+
it "returns a set of all keys in the state" do
|
30
|
+
expect(subject.keys).to eq(Set.new(["current_node", "path", "responses"]))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#put" do
|
35
|
+
it "returns a copy of state and leaves orignal unchanged" do
|
36
|
+
new_state = subject.put(:a, 1)
|
37
|
+
expect { subject.get(:a) }.to raise_error
|
38
|
+
expect(new_state.get(:a)).to eq 1
|
39
|
+
end
|
40
|
+
|
41
|
+
it "by string or symbol" do
|
42
|
+
s2 = subject.put(:b, 1)
|
43
|
+
expect(s2.get(:b)).to eq(1)
|
44
|
+
expect(s2.get("b")).to eq(1)
|
45
|
+
s3 = subject.put("b", 2)
|
46
|
+
expect(s3.get(:b)).to eq(2)
|
47
|
+
expect(s3.get("b")).to eq(2)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "lambda values" do
|
52
|
+
let(:predicate) { double("predicate", call: true) }
|
53
|
+
subject(:state) {
|
54
|
+
described_class.new(current_node: :start_state, predicate?: predicate)
|
55
|
+
}
|
56
|
+
|
57
|
+
describe "#get" do
|
58
|
+
it "evaluates lambda with state" do
|
59
|
+
expect(predicate).to receive(:call).with(state).and_return(true)
|
60
|
+
expect(state.get(:predicate?)).to eq(true)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "caches the result of evaluating the lambda" do
|
64
|
+
expect(predicate).to receive(:call).once
|
65
|
+
state.get(:predicate?)
|
66
|
+
state.get(:predicate?)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#==" do
|
71
|
+
let(:l1) { ->(state) { true } }
|
72
|
+
let(:l2) { ->(state) { true } }
|
73
|
+
|
74
|
+
let(:state_with_l1a) { described_class.new(current_node: "red", pred: l1)}
|
75
|
+
let(:state_with_l1b) { described_class.new(current_node: "red", pred: l1)}
|
76
|
+
let(:state_with_l2) { described_class.new(current_node: "red", pred: l2)}
|
77
|
+
|
78
|
+
it "is equal if identical lambdas" do
|
79
|
+
expect(state_with_l1a).to eq(state_with_l1b)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "is not equal if different lambdas" do
|
83
|
+
expect(state_with_l1a).not_to eq(state_with_l2)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#==" do
|
89
|
+
let(:s1) { described_class.new(current_node: "red") }
|
90
|
+
let(:s2) { described_class.new(current_node: "red") }
|
91
|
+
let(:s3) { described_class.new(current_node: "green") }
|
92
|
+
let(:s4) { described_class.new(current_node: "red", a: 1) }
|
93
|
+
|
94
|
+
it "is true if two states have the same keys and values" do
|
95
|
+
expect(s1).to eq(s2)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "is false if two states have the same keys with different values" do
|
99
|
+
expect(s1).not_to eq(s3)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "is false if one state is a subset of the other" do
|
103
|
+
expect(s1).not_to eq(s4)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'smartdown/engine/transition'
|
2
|
+
require 'smartdown/engine/state'
|
3
|
+
require 'smartdown/model/rule'
|
4
|
+
require 'smartdown/model/nested_rule'
|
5
|
+
|
6
|
+
describe Smartdown::Engine::Transition do
|
7
|
+
let(:current_node_name) { "q1" }
|
8
|
+
let(:start_state) { Smartdown::Engine::State.new(current_node: current_node_name) }
|
9
|
+
let(:input) { "yes" }
|
10
|
+
subject(:transition) { described_class.new(start_state, current_node, input, predicate_evaluator: predicate_evaluator) }
|
11
|
+
let(:predicate_evaluator) { instance_double("Smartdown::Engine::PredicateEvaluator") }
|
12
|
+
let(:state_including_input) {
|
13
|
+
start_state.put(current_node.name, input)
|
14
|
+
}
|
15
|
+
|
16
|
+
context "no next node rules" do
|
17
|
+
let(:current_node) {
|
18
|
+
Smartdown::Model::Node.new(current_node_name, [])
|
19
|
+
}
|
20
|
+
|
21
|
+
describe "#next_node" do
|
22
|
+
it "raises IndeterminateNextNode" do
|
23
|
+
expect { transition.next_node }.to raise_error(Smartdown::Engine::IndeterminateNextNode)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "next node rules defined with a simple rule" do
|
29
|
+
let(:predicate1) { double("predicate1") }
|
30
|
+
let(:outcome_name1) { "o1" }
|
31
|
+
let(:predicate2) { double("predicate2") }
|
32
|
+
let(:outcome_name2) { "o2" }
|
33
|
+
|
34
|
+
let(:current_node) {
|
35
|
+
Smartdown::Model::Node.new(
|
36
|
+
current_node_name,
|
37
|
+
[
|
38
|
+
Smartdown::Model::NextNodeRules.new(
|
39
|
+
[
|
40
|
+
Smartdown::Model::Rule.new(
|
41
|
+
predicate1, outcome_name1
|
42
|
+
),
|
43
|
+
Smartdown::Model::Rule.new(
|
44
|
+
predicate2, outcome_name2
|
45
|
+
)
|
46
|
+
]
|
47
|
+
)
|
48
|
+
]
|
49
|
+
)
|
50
|
+
}
|
51
|
+
|
52
|
+
describe "#next_node" do
|
53
|
+
it "invokes the predicate evaluator with the predicate and state including the input value" do
|
54
|
+
expect(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(true)
|
55
|
+
|
56
|
+
transition.next_node
|
57
|
+
end
|
58
|
+
|
59
|
+
it "invokes the predicate evaluator for each rule in turn" do
|
60
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(false)
|
61
|
+
expect(predicate_evaluator).to receive(:evaluate).with(predicate2, state_including_input).and_return(true)
|
62
|
+
|
63
|
+
transition.next_node
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns the name of the next node" do
|
67
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(false)
|
68
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate2, state_including_input).and_return(true)
|
69
|
+
|
70
|
+
expect(transition.next_node).to eq(outcome_name2)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#next_state" do
|
75
|
+
before(:each) do
|
76
|
+
allow(predicate_evaluator).to receive(:evaluate).and_return(true)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns a state including a record of responses, path, and new current_node" do
|
80
|
+
expected_state = start_state
|
81
|
+
.put(:responses, [input])
|
82
|
+
.put(:path, [current_node_name])
|
83
|
+
.put(:current_node, outcome_name1)
|
84
|
+
.put(current_node.name, input)
|
85
|
+
|
86
|
+
expect(transition.next_state).to eq(expected_state)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "next node rules defined with nested rule" do
|
92
|
+
let(:predicate1) { double("predicate1") }
|
93
|
+
let(:predicate2) { double("predicate2") }
|
94
|
+
let(:predicate3) { double("predicate3") }
|
95
|
+
let(:outcome_name1) { "o1" }
|
96
|
+
let(:outcome_name2) { "o2" }
|
97
|
+
|
98
|
+
let(:current_node) {
|
99
|
+
Smartdown::Model::Node.new(
|
100
|
+
current_node_name,
|
101
|
+
[
|
102
|
+
Smartdown::Model::NextNodeRules.new(
|
103
|
+
[
|
104
|
+
Smartdown::Model::NestedRule.new(
|
105
|
+
predicate1, [
|
106
|
+
Smartdown::Model::Rule.new(
|
107
|
+
predicate2, outcome_name1
|
108
|
+
),
|
109
|
+
Smartdown::Model::Rule.new(
|
110
|
+
predicate3, outcome_name2
|
111
|
+
)
|
112
|
+
]
|
113
|
+
)
|
114
|
+
]
|
115
|
+
)
|
116
|
+
]
|
117
|
+
)
|
118
|
+
}
|
119
|
+
|
120
|
+
describe "#next_node" do
|
121
|
+
context "p1 false" do
|
122
|
+
it "invokes the predicate evaluator with each predicate in turn" do
|
123
|
+
expect(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(false)
|
124
|
+
|
125
|
+
expect { transition.next_node }.to raise_error(Smartdown::Engine::IndeterminateNextNode)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "p1 and p2 true" do
|
130
|
+
it "invokes the predicate evaluator with each predicate in turn" do
|
131
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(true)
|
132
|
+
expect(predicate_evaluator).to receive(:evaluate).with(predicate2, state_including_input).and_return(true)
|
133
|
+
|
134
|
+
expect(transition.next_node).to eq(outcome_name1)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "p1 true, p2 false, p3 true" do
|
139
|
+
it "invokes the predicate evaluator with each predicate in turn" do
|
140
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate1, state_including_input).and_return(true)
|
141
|
+
allow(predicate_evaluator).to receive(:evaluate).with(predicate2, state_including_input).and_return(false)
|
142
|
+
expect(predicate_evaluator).to receive(:evaluate).with(predicate3, state_including_input).and_return(true)
|
143
|
+
|
144
|
+
expect(transition.next_node).to eq(outcome_name2)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'smartdown/engine'
|
2
|
+
|
3
|
+
describe Smartdown::Engine do
|
4
|
+
let(:flow) {
|
5
|
+
build_flow("check-uk-visa") do
|
6
|
+
node("check-uk-visa") do
|
7
|
+
heading("Check uk visa")
|
8
|
+
paragraph("This is the paragraph")
|
9
|
+
start_button("what_passport_do_you_have?")
|
10
|
+
next_node_rules do
|
11
|
+
rule do
|
12
|
+
named_predicate("otherwise")
|
13
|
+
outcome("what_passport_do_you_have?")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
node("what_passport_do_you_have?") do
|
19
|
+
heading("What passport do you have?")
|
20
|
+
multiple_choice(
|
21
|
+
greek: "Greek",
|
22
|
+
british: "British",
|
23
|
+
usa: "USA"
|
24
|
+
)
|
25
|
+
next_node_rules do
|
26
|
+
rule do
|
27
|
+
named_predicate("eea_passport?")
|
28
|
+
outcome("outcome_no_visa_needed")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
subject(:engine) { Smartdown::Engine.new(flow) }
|
36
|
+
let(:start_state) {
|
37
|
+
engine.default_start_state
|
38
|
+
.put(:eea_passport?, ->(state) {
|
39
|
+
%w{greek british}.include?(state.get(:what_passport_do_you_have?))
|
40
|
+
})
|
41
|
+
.put(:otherwise, true)
|
42
|
+
}
|
43
|
+
|
44
|
+
describe "#process" do
|
45
|
+
subject { engine.process(responses, start_state) }
|
46
|
+
|
47
|
+
context "start button response only" do
|
48
|
+
let(:responses) { %w{yes} }
|
49
|
+
|
50
|
+
it "is on what_passport_do_you_have?" do
|
51
|
+
expect(subject.get(:current_node)).to eq("what_passport_do_you_have?")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "has recorded input" do
|
55
|
+
expect(subject.get("check-uk-visa")).to eq("yes")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "greek passport" do
|
60
|
+
let(:responses) { %w{yes greek} }
|
61
|
+
|
62
|
+
it "is on what_passport_do_you_have?" do
|
63
|
+
expect(subject.get(:current_node)).to eq("outcome_no_visa_needed")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "has recorded input" do
|
67
|
+
expect(subject.get("what_passport_do_you_have?")).to eq("greek")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "USA passport" do
|
72
|
+
let(:responses) { %w{yes usa} }
|
73
|
+
|
74
|
+
it "raises IndeterminateNextNode error" do
|
75
|
+
expect { subject }.to raise_error(Smartdown::Engine::IndeterminateNextNode)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
cover sheet
|
@@ -0,0 +1 @@
|
|
1
|
+
outcome one
|
@@ -0,0 +1 @@
|
|
1
|
+
question one
|
@@ -0,0 +1 @@
|
|
1
|
+
scenario one
|
@@ -0,0 +1,17 @@
|
|
1
|
+
name: border_control?
|
2
|
+
|
3
|
+
# Will you pass through UK Border Control?
|
4
|
+
|
5
|
+
You might pass through UK Border Control even if you don't leave the airport -
|
6
|
+
eg your bags aren't checked through and you need to collect them before transferring
|
7
|
+
to your outbound flight.
|
8
|
+
|
9
|
+
* yes: Yes
|
10
|
+
* no: No
|
11
|
+
|
12
|
+
-----------
|
13
|
+
|
14
|
+
# Next node
|
15
|
+
|
16
|
+
* border_control is 'yes' => transit
|
17
|
+
* border_control is 'no' => outcome_visa
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'smartdown/model/flow'
|
2
|
+
require 'smartdown/model/node'
|
3
|
+
|
4
|
+
describe Smartdown::Model::Flow do
|
5
|
+
let(:flow_name) { "my_name" }
|
6
|
+
let(:nodes) { [] }
|
7
|
+
subject(:flow) { Smartdown::Model::Flow.new(flow_name, nodes) }
|
8
|
+
|
9
|
+
it "has a name" do
|
10
|
+
expect(subject.name).to eq(flow_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "no nodes" do
|
14
|
+
it "has no nodes" do
|
15
|
+
expect(flow.nodes).to eq([])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "one node" do
|
20
|
+
let(:node_name) { "chocolate?" }
|
21
|
+
let(:node) {
|
22
|
+
instance_double("Smartdown::Model::Node", name: node_name)
|
23
|
+
}
|
24
|
+
let(:nodes) { [node] }
|
25
|
+
|
26
|
+
describe "#nodes" do
|
27
|
+
it "returns a list with the node" do
|
28
|
+
expect(flow.nodes).to eq([node])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#node" do
|
33
|
+
it "fetches the named node" do
|
34
|
+
expect(subject.node(node_name)).to eq(node)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises if node not found" do
|
38
|
+
expect { subject.node("undefined node") }.to raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'smartdown/model/node'
|
2
|
+
|
3
|
+
describe Smartdown::Model::Node do
|
4
|
+
let(:name) { "my node" }
|
5
|
+
let(:elements) { [] }
|
6
|
+
|
7
|
+
describe "#new" do
|
8
|
+
subject(:node) { described_class.new(name, elements) }
|
9
|
+
|
10
|
+
it "accepts name and list of body blocks" do
|
11
|
+
expect(node.name).to eq(name)
|
12
|
+
expect(node.elements).to eq(elements)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "no front matter" do
|
16
|
+
let(:empty_front_matter) { Smartdown::Model::FrontMatter.new({}) }
|
17
|
+
|
18
|
+
it "defaults to empty" do
|
19
|
+
expect(node.front_matter).to eq(empty_front_matter)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "front matter" do
|
24
|
+
let(:front_matter) { Smartdown::Model::FrontMatter.new({a: "1"}) }
|
25
|
+
subject(:node) { described_class.new(name, elements, front_matter) }
|
26
|
+
|
27
|
+
it "uses it" do
|
28
|
+
expect(node.front_matter).to eq(front_matter)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'smartdown/parser/element/front_matter'
|
2
|
+
|
3
|
+
describe Smartdown::Parser::Base do
|
4
|
+
|
5
|
+
subject(:parser) { described_class.new }
|
6
|
+
|
7
|
+
describe "#ws" do
|
8
|
+
subject { parser.ws }
|
9
|
+
it { should parse("\n") }
|
10
|
+
it { should parse(" ") }
|
11
|
+
it { should parse(" ") }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#eof" do
|
15
|
+
subject { parser.eof }
|
16
|
+
|
17
|
+
it { should parse("") }
|
18
|
+
it { should_not parse(" ") }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#newline" do
|
22
|
+
subject { parser.newline }
|
23
|
+
|
24
|
+
# Only one newline
|
25
|
+
it { should parse("\r") }
|
26
|
+
it { should parse("\r\n") }
|
27
|
+
it { should parse("\n\r") }
|
28
|
+
it { should parse("\n") }
|
29
|
+
|
30
|
+
# Not multiple
|
31
|
+
it { should_not parse("\n\n") }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#whitespace_terminated_string" do
|
35
|
+
subject { parser.whitespace_terminated_string }
|
36
|
+
|
37
|
+
it { should parse("a b c") }
|
38
|
+
it { should_not parse(" a") }
|
39
|
+
it { should_not parse("a ") }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "identifier" do
|
43
|
+
subject { parser.identifier }
|
44
|
+
|
45
|
+
it { should parse("abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-123") }
|
46
|
+
it { should_not parse("abc_ABC-123?") }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'smartdown/parser/directory_input'
|
2
|
+
|
3
|
+
shared_examples "flow input interface" do
|
4
|
+
it { should respond_to(:coversheet) }
|
5
|
+
it { should respond_to(:questions) }
|
6
|
+
it { should respond_to(:outcomes) }
|
7
|
+
it { should respond_to(:scenarios) }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Smartdown::Parser::DirectoryInput do
|
11
|
+
it_should_behave_like "flow input interface"
|
12
|
+
|
13
|
+
let(:coversheet_file) {
|
14
|
+
Pathname.new("../../fixtures/directory_input/cover-sheet.txt").expand_path(__FILE__)
|
15
|
+
}
|
16
|
+
|
17
|
+
subject(:input) { described_class.new(coversheet_file) }
|
18
|
+
|
19
|
+
describe "#coversheet" do
|
20
|
+
subject { input.coversheet }
|
21
|
+
|
22
|
+
it { should be_a(Smartdown::Parser::InputFile) }
|
23
|
+
|
24
|
+
it "has name" do
|
25
|
+
expect(input.coversheet.name).to eq("cover-sheet")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "reads the file contents" do
|
29
|
+
expect(input.coversheet.read).to eq("cover sheet\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#questions" do
|
34
|
+
it "returns an InputFile for every file in the questions folder" do
|
35
|
+
expect(input.questions).to match([instance_of(Smartdown::Parser::InputFile)])
|
36
|
+
expect(input.questions.first.name).to eq("q1")
|
37
|
+
expect(input.questions.first.read).to eq("question one\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#outcomes" do
|
42
|
+
it "returns an InputFile for every file in the outcomes folder" do
|
43
|
+
expect(input.outcomes).to match([instance_of(Smartdown::Parser::InputFile)])
|
44
|
+
expect(input.outcomes.first.name).to eq("o1")
|
45
|
+
expect(input.outcomes.first.read).to eq("outcome one\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#scenarios" do
|
50
|
+
it "returns an InputFile for every file in the scenarios folder" do
|
51
|
+
expect(input.scenarios).to match([instance_of(Smartdown::Parser::InputFile)])
|
52
|
+
expect(input.scenarios.first.name).to eq("s1")
|
53
|
+
expect(input.scenarios.first.read).to eq("scenario one\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'smartdown/parser/element/front_matter'
|
2
|
+
require 'smartdown/parser/node_interpreter'
|
3
|
+
|
4
|
+
describe Smartdown::Parser::Element::FrontMatter do
|
5
|
+
|
6
|
+
subject(:parser) { described_class.new }
|
7
|
+
|
8
|
+
it { should parse("a: 1\n").as(front_matter: [{name: "a", value: "1"}]) }
|
9
|
+
it { should parse("a: 1\nb: 2\n").as(front_matter: [{name: "a", value: "1"}, {name: "b", value: "2"}]) }
|
10
|
+
|
11
|
+
describe "transformed" do
|
12
|
+
let(:node_name) { "my_node" }
|
13
|
+
let(:source) { "a: 1\n" }
|
14
|
+
subject(:transformed) {
|
15
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
16
|
+
}
|
17
|
+
|
18
|
+
it { should eq([Smartdown::Model::FrontMatter.new("a"=>"1")]) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'smartdown/parser/element/markdown_heading'
|
2
|
+
require 'smartdown/parser/node_interpreter'
|
3
|
+
|
4
|
+
describe Smartdown::Parser::Element::MarkdownHeading do
|
5
|
+
|
6
|
+
subject(:parser) { described_class.new }
|
7
|
+
let(:node_name) { "my_node" }
|
8
|
+
let(:heading_content) { "My heading"}
|
9
|
+
let(:source) { "# #{heading_content}" }
|
10
|
+
|
11
|
+
it { should parse(source).as(h1: heading_content) }
|
12
|
+
it { should parse("# heading\n").as(h1: "heading") }
|
13
|
+
it { should parse("# heading \n").as(h1: "heading") }
|
14
|
+
it { should parse("# heading ").as(h1: "heading") }
|
15
|
+
|
16
|
+
describe "transformed" do
|
17
|
+
subject(:transformed) {
|
18
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
19
|
+
}
|
20
|
+
|
21
|
+
it { should eq(Smartdown::Model::Element::MarkdownHeading.new(heading_content)) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'smartdown/parser/element/markdown_paragraph'
|
2
|
+
require 'smartdown/parser/node_interpreter'
|
3
|
+
|
4
|
+
describe Smartdown::Parser::Element::MarkdownParagraph do
|
5
|
+
|
6
|
+
subject(:parser) { described_class.new }
|
7
|
+
let(:node_name) { "my_node" }
|
8
|
+
|
9
|
+
it { should parse("My para").as(p: "My para") }
|
10
|
+
it { should parse("My para\n").as(p: "My para\n") }
|
11
|
+
it { should parse(" My para").as(p: " My para") }
|
12
|
+
it { should parse(" My para\nsecond line").as(p: " My para\nsecond line") }
|
13
|
+
it { should parse(" My para\nsecond line\n").as(p: " My para\nsecond line\n") }
|
14
|
+
it { should parse(" My para\nsecond line \n").as(p: " My para\nsecond line \n") }
|
15
|
+
it { should_not parse("Para1\n\nPara2") }
|
16
|
+
|
17
|
+
it { should parse("a b") }
|
18
|
+
|
19
|
+
describe "transformed" do
|
20
|
+
let(:content) { "My para" }
|
21
|
+
subject(:transformed) {
|
22
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, content, parser: parser).interpret
|
23
|
+
}
|
24
|
+
|
25
|
+
it { should eq(Smartdown::Model::Element::MarkdownParagraph.new(content)) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|