diff --git a/doc/smart-answer-flows.md b/doc/smart-answer-flows.md
index 0ccbdec8398..826afbfacc1 100644
--- a/doc/smart-answer-flows.md
+++ b/doc/smart-answer-flows.md
@@ -107,19 +107,6 @@ next_node(permitted: permitted_next_nodes) do |response|
 end
 ```
 
-#### DEPRECATED: Using predicates
-
-This is the same example from above expressed using predicates:
-
-```ruby
-next_node_if(:green, responded_with('green')) )
-next_node(:red)
-```
-
-The `responded_with` function actually returns a [predicate](http://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29) which will be invoked during processing. If the predicate returns `true` then the `:green` node will be next, otherwise the next rule will be evaluated. In this case the next rule says `:red` is the next node with no condition.
-
-See [Smart Answer predicates](smart-answers-predicates.md) for more detailed information about this style.
-
 ### Storing data for later use
 
 You can use the `precalculate`, `next_node_calculation`, `save_input_as` and `calculate` methods to store data for later use.
diff --git a/doc/smart-answers-predicates.md b/doc/smart-answers-predicates.md
deleted file mode 100644
index f8071a4fae4..00000000000
--- a/doc/smart-answers-predicates.md
+++ /dev/null
@@ -1,47 +0,0 @@
-## Predicate helpers
-
-* `responded_with(value)` - `value` can be either a string or an array of values
-* `variable_matches(varname, value)` - `varname` is a symbol representing the name of the variable to test, `value` can be either a single value or an array
-* `response_has_all_of(required_responses)` - only for checkbox questions, true if all of the required responses were checked. `required_responses` can be a single value or an array.
-* `response_is_one_of(responses)` -  only for checkbox questions, true if ANY of the responses were checked. `responses` can be a single value or an array.
-
-## Combining predicates
-
-Predicates can be combined using logical conjunctions `|` or `&`:
-
-```ruby
-next_node_if(:orange, variable_matches(:first_colour, "red") & variable_matches(:second_colour, "yellow"))
-next_node_if(:monochrome, variable_matches(:first_colour, "black") | variable_matches(:second_colour, "white"))
-```
-
-## Structuring rules by nesting
-
-Predicates can also be organised by nesting using `on_condition`, e.g.
-
-```ruby
-on_condition(responded_with("red")) do
-  next_node_if(:orange, variable_matches(:first_color, "yellow"))
-  next_node(:red)
-end
-next_node(:blue)
-```
-
-Here's a truth table for the above scenario:
-
-```
-|       | Yellow | other
-| Red   | orange | red
-| other | blue   | blue
-```
-
-## Defining named predicates
-
-Named predicates can also be defined using
-
-```ruby
-define_predicate(:can_has_cheesburger?) do |response|
-  # logic hereā€¦
-end
-
-next_node_if(:something, can_has_cheesburger?)
-```
diff --git a/lib/graph_presenter.rb b/lib/graph_presenter.rb
index 5eb820bc114..9ee9052b995 100644
--- a/lib/graph_presenter.rb
+++ b/lib/graph_presenter.rb
@@ -13,9 +13,6 @@ def adjacency_list
       adjacency_list = {}
       @flow.questions.each do |node|
         adjacency_list[node.name] = []
-        node.next_node_function_chain.each do |(nextnode, predicates)|
-          adjacency_list[node.name] << [nextnode, predicates.map(&:label).compact.join(" AND\n")]
-        end
         node.permitted_next_nodes.each do |permitted_next_node|
           existing_next_nodes = adjacency_list[node.name].map(&:first)
           unless existing_next_nodes.include?(permitted_next_node)
diff --git a/lib/smart_answer/calculators/registrations_data_query.rb b/lib/smart_answer/calculators/registrations_data_query.rb
index e47fec5123c..c20e8f98627 100644
--- a/lib/smart_answer/calculators/registrations_data_query.rb
+++ b/lib/smart_answer/calculators/registrations_data_query.rb
@@ -60,10 +60,6 @@ def commonwealth_country?(country_slug)
       COMMONWEALTH_COUNTRIES.include?(country_slug)
     end
 
-    def responded_with_commonwealth_country?
-      SmartAnswer::Predicate::RespondedWith.new(COMMONWEALTH_COUNTRIES, "commonwealth country")
-    end
-
     def has_consulate?(country_slug)
       COUNTRIES_WITH_CONSULATES.include?(country_slug)
     end
diff --git a/lib/smart_answer/predicate/base.rb b/lib/smart_answer/predicate/base.rb
deleted file mode 100644
index c200f72d3f8..00000000000
--- a/lib/smart_answer/predicate/base.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class Base
-      def initialize(label = nil, callable = nil)
-        @label = label
-        @callable = callable
-      end
-
-      def call(state, input)
-        @callable.call(state, input)
-      end
-
-      def or(other, new_label = nil)
-        SmartAnswer::Predicate::Base.new(new_label || "#{self.label} | #{other.label}", ->(state, input) {
-          self.call(state, input) || other.call(state, input)
-        })
-      end
-
-      alias_method :|, :or
-
-      def and(other, new_label = nil)
-        SmartAnswer::Predicate::Base.new(new_label || "#{self.label} AND #{other.label}", ->(state, input) {
-          self.call(state, input) && other.call(state, input)
-        })
-      end
-
-      alias_method :&, :and
-
-      def label
-        @label || "-- undefined --"
-      end
-
-    end
-  end
-end
diff --git a/lib/smart_answer/predicate/callable.rb b/lib/smart_answer/predicate/callable.rb
deleted file mode 100644
index 31bb04efdca..00000000000
--- a/lib/smart_answer/predicate/callable.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class Callable < Base
-      def initialize(label = nil, callable = nil, &block)
-        callable_expecting_state_as_binding = block_given? ? block : callable
-        callable_taking_state_as_arg = ->(state, input) {
-          if callable_expecting_state_as_binding.arity == 0
-            state.instance_exec &callable_expecting_state_as_binding
-          else
-            state.instance_exec(input, &callable_expecting_state_as_binding)
-          end
-        }
-
-        super(label || "--defined by code--", callable_taking_state_as_arg)
-      end
-    end
-  end
-end
diff --git a/lib/smart_answer/predicate/responded_with.rb b/lib/smart_answer/predicate/responded_with.rb
deleted file mode 100644
index 1cdb13f30ff..00000000000
--- a/lib/smart_answer/predicate/responded_with.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class RespondedWith < Base
-      attr_reader :acceptable_responses
-
-      def initialize(acceptable_responses, label = nil)
-        @acceptable_responses = [*acceptable_responses]
-        @label = label
-      end
-
-      def call(state, response)
-        @acceptable_responses.include?(response.to_s)
-      end
-
-      alias_method :|, :or
-
-      def label
-        @label || @acceptable_responses.join(" | ")
-      end
-    end
-  end
-end
diff --git a/lib/smart_answer/predicate/response_has_all_of.rb b/lib/smart_answer/predicate/response_has_all_of.rb
deleted file mode 100644
index 74ad3e44f5b..00000000000
--- a/lib/smart_answer/predicate/response_has_all_of.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class ResponseHasAllOf < Base
-      def initialize(required_responses)
-        @required_responses = [*required_responses]
-      end
-
-      def call(state, response)
-        (response.split(",") & @required_responses).size == @required_responses.size
-      end
-
-      def label
-        @required_responses.join(" & ")
-      end
-    end
-  end
-end
diff --git a/lib/smart_answer/predicate/response_is_one_of.rb b/lib/smart_answer/predicate/response_is_one_of.rb
deleted file mode 100644
index 2bb8696469a..00000000000
--- a/lib/smart_answer/predicate/response_is_one_of.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class ResponseIsOneOf < Base
-      def initialize(accepted_responses)
-        @accepted_responses = [*accepted_responses]
-      end
-
-      def call(state, response)
-        (response.split(",") & @accepted_responses).any?
-      end
-
-      def label
-        @accepted_responses.join(" | ")
-      end
-    end
-  end
-end
diff --git a/lib/smart_answer/predicate/variable_matches.rb b/lib/smart_answer/predicate/variable_matches.rb
deleted file mode 100644
index ae71071b7e3..00000000000
--- a/lib/smart_answer/predicate/variable_matches.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-module SmartAnswer
-  module Predicate
-    class VariableMatches < Base
-      attr_reader :variable_name, :acceptable_responses
-
-      def initialize(variable_name, acceptable_responses, match_description = nil, label = nil)
-        @variable_name = variable_name
-        @acceptable_responses = [*acceptable_responses]
-        @match_description = match_description
-        @label = label
-      end
-
-      def call(state, response)
-        acceptable_responses.include?(state.send(variable_name))
-      end
-
-      def or(other)
-        if other.is_a?(VariableMatches) && other.variable_name == self.variable_name
-          super(other, "#{@variable_name} == #{self.match_description} | #{other.match_description}")
-        else
-          super
-        end
-      end
-
-      alias_method :|, :or
-
-      def match_description
-        @match_description || generate_match_description
-      end
-
-      def label
-        @label || "#{variable_name} == #{match_description}"
-      end
-
-    private
-      def generate_match_description
-        if multiple_acceptable_responses?
-          wrap_in_braces(acceptable_responses)
-        else
-          acceptable_responses.first || ""
-        end
-      end
-
-      def multiple_acceptable_responses?
-        acceptable_responses.size > 1
-      end
-
-      def wrap_in_braces(set)
-        "{ #{set.join(" | ")} }"
-      end
-    end
-  end
-end
diff --git a/lib/smart_answer/question/base.rb b/lib/smart_answer/question/base.rb
index 730c0f7a007..56b7a33352b 100644
--- a/lib/smart_answer/question/base.rb
+++ b/lib/smart_answer/question/base.rb
@@ -1,17 +1,13 @@
 module SmartAnswer
   module Question
     class Base < Node
-      attr_reader :next_node_function_chain
       class NextNodeUndefined < StandardError; end
 
       def initialize(flow, name, options = {}, &block)
         @save_input_as = nil
         @validations ||= []
-        @next_node_function_chain ||= []
         @default_next_node_function ||= lambda {|_|}
         @permitted_next_nodes = []
-        @predicate_stack = []
-        @predicates = {}
         @uses_erb_template = options[:use_erb_template]
         super
       end
@@ -24,18 +20,13 @@ def next_node(next_node = nil, permitted: [], &block)
           @permitted_next_nodes += permitted
           @default_next_node_function = block
         elsif next_node
-          next_node_if(next_node)
+          @permitted_next_nodes = [next_node]
+          @default_next_node_function = proc { next_node }
         else
           raise ArgumentError
         end
       end
 
-      def next_node_if(next_node, *predicates, &block)
-        predicates << block if block_given?
-        @next_node_function_chain << [next_node, normalize_predicates(@predicate_stack + predicates)]
-        @permitted_next_nodes << next_node
-      end
-
       def validate(message = nil, &block)
         @validations << [message, block]
       end
@@ -46,7 +37,7 @@ def permitted_next_nodes(*args)
 
       def next_node_for(current_state, input)
         validate!(current_state, input)
-        next_node = next_node_from_function_chain(current_state, input) || next_node_from_default_function(current_state, input)
+        next_node = next_node_from_default_function(current_state, input)
         responses_and_input = current_state.responses + [input]
         raise NextNodeUndefined.new("Next node undefined. Node: #{current_state.current_node}. Responses: #{responses_and_input}") unless next_node
         unless @permitted_next_nodes.include?(next_node)
@@ -74,47 +65,6 @@ def transition(current_state, raw_input)
         new_state
       end
 
-      # Within an #on_condition block, all #next_node and #next_node_if
-      # clauses must additionally satisfy the given predicate. Nesting of
-      # #on_condition blocks is permitted.
-      #
-      # Example:
-      #
-      # on_condition(->(r) {r == 'tourism'}) do
-      #   next_node_if(:outcome_visit_waiver) { %w(oman qatar united-arab-emirates).include?(passport_country) }
-      #   next_node_if(:outcome_taiwan_exception) { %w(taiwan).include?(passport_country) }
-      #   next_node_if(:outcome_school_n) do
-      #     country_group_non_visa_national.include?(passport_country) or country_group_ukot.include?(passport_country))
-      #   end
-      #   next_node(:outcome_general_y)
-      # end
-      def on_condition(predicate, &block)
-        @predicate_stack << predicate
-        instance_eval(&block)
-        @predicate_stack.pop
-      end
-
-      def responded_with(acceptable_responses)
-        SmartAnswer::Predicate::RespondedWith.new(acceptable_responses)
-      end
-
-      def variable_matches(variable_name, acceptable_responses)
-        SmartAnswer::Predicate::VariableMatches.new(variable_name, acceptable_responses)
-      end
-
-      def define_predicate(identifier, label = nil, &block)
-        raise "method #{identifier} already defined" if self.class.method_defined?(identifier)
-        @predicates[identifier] = SmartAnswer::Predicate::Callable.new(label || identifier.to_s, &block)
-      end
-
-      def respond_to_missing?(method, include_private = false)
-        @predicates.has_key?(method)
-      end
-
-      def method_missing(method, *args, &block)
-        @predicates.fetch(method)
-      end
-
       def parse_input(raw_input)
         raw_input
       end
@@ -148,25 +98,6 @@ def validate!(current_state, input)
         end
       end
 
-      def normalize_predicates(predicates)
-        predicates.map do |predicate|
-          if predicate.is_a?(SmartAnswer::Predicate::Base)
-            predicate
-          else
-            SmartAnswer::Predicate::Callable.new(nil, predicate)
-          end
-        end
-      end
-
-      def next_node_from_function_chain(current_state, input)
-        found = @next_node_function_chain.find do |(_, predicates)|
-          predicates.all? do |predicate|
-            predicate.call(current_state, input)
-          end
-        end
-        found && found.first
-      end
-
       def next_node_from_default_function(current_state, input)
         current_state.instance_exec(input, &@default_next_node_function)
       end
diff --git a/lib/smart_answer/question/checkbox.rb b/lib/smart_answer/question/checkbox.rb
index 1a250adc679..e666327d6d6 100644
--- a/lib/smart_answer/question/checkbox.rb
+++ b/lib/smart_answer/question/checkbox.rb
@@ -33,21 +33,6 @@ def parse_input(raw_input)
       def to_response(input)
         input.split(',').reject {|v| v == NONE_OPTION }
       end
-
-      # Returns a predicate function which when given a response
-      # returns a boolean indicating whether the response was one
-      # of the accepted responses.
-      def response_is_one_of(accepted_responses)
-        SmartAnswer::Predicate::ResponseIsOneOf.new(accepted_responses)
-      end
-
-      def response_has_all_of(required_responses)
-        SmartAnswer::Predicate::ResponseHasAllOf.new(required_responses)
-      end
-
-      def response_is_only(accepted_response)
-        ->(response) { response.split(",") == [accepted_response] }
-      end
     end
   end
 end
diff --git a/test/data/marriage-abroad-files.yml b/test/data/marriage-abroad-files.yml
index fab18b4ff8f..914d5cea98e 100644
--- a/test/data/marriage-abroad-files.yml
+++ b/test/data/marriage-abroad-files.yml
@@ -123,4 +123,4 @@ lib/smart_answer_flows/marriage-abroad/questions/what_is_your_partners_nationali
 lib/smart_answer_flows/shared/_overseas_passports_embassies.govspeak.erb: 2f521bae99c2f48b49d07bcb283a8063
 lib/smart_answer/calculators/marriage_abroad_data_query.rb: b69ed89bee92f2361f86e7509fc27ebf
 lib/smart_answer/calculators/country_name_formatter.rb: 0cb274748f0daf3965451b6230c183fd
-lib/smart_answer/calculators/registrations_data_query.rb: cbb0fad828d74606eb708d3442cf4ca5
+lib/smart_answer/calculators/registrations_data_query.rb: 563c5db7d7272bc07bd636a92b40f3ce
diff --git a/test/data/register-a-birth-files.yml b/test/data/register-a-birth-files.yml
index e00e239f855..6de695f6614 100644
--- a/test/data/register-a-birth-files.yml
+++ b/test/data/register-a-birth-files.yml
@@ -23,7 +23,7 @@ lib/data/rates/births_and_deaths_document_return_fees.yml: 4ac203e9fd076c12f62b5
 lib/data/translators.yml: d86f628f0b85e24ffb0dc0ec77401387
 lib/data/registrations.yml: 2eb5c61678f21bd9cc06af67c230279f
 lib/smart_answer/calculators/country_name_formatter.rb: 0cb274748f0daf3965451b6230c183fd
-lib/smart_answer/calculators/registrations_data_query.rb: cbb0fad828d74606eb708d3442cf4ca5
+lib/smart_answer/calculators/registrations_data_query.rb: 563c5db7d7272bc07bd636a92b40f3ce
 lib/smart_answer/calculators/translator_links.rb: 079592f6c5ede06d7c6ae4e8c5553127
 test/fixtures/worldwide_locations.yml: 063711faceec3a4081d6d5fb386c8029
 test/fixtures/worldwide/afghanistan_organisations.json: 1fd89b60eb079c7b3fddcab985f37366
diff --git a/test/data/register-a-death-files.yml b/test/data/register-a-death-files.yml
index 6ffa16a6e7d..8ba46117492 100644
--- a/test/data/register-a-death-files.yml
+++ b/test/data/register-a-death-files.yml
@@ -20,7 +20,7 @@ lib/smart_answer_flows/shared/births_and_deaths_registration/_button.govspeak.er
 lib/data/rates/births_and_deaths_document_return_fees.yml: 4ac203e9fd076c12f62b57f8d1b64ffc
 lib/data/translators.yml: d86f628f0b85e24ffb0dc0ec77401387
 test/fixtures/worldwide/italy_organisations.json: dc5feb424edeeb12835be916605caf46
-lib/smart_answer/calculators/registrations_data_query.rb: cbb0fad828d74606eb708d3442cf4ca5
+lib/smart_answer/calculators/registrations_data_query.rb: 563c5db7d7272bc07bd636a92b40f3ce
 lib/smart_answer/calculators/country_name_formatter.rb: 0cb274748f0daf3965451b6230c183fd
 lib/smart_answer/calculators/translator_links.rb: 079592f6c5ede06d7c6ae4e8c5553127
 test/fixtures/worldwide_locations.yml: 063711faceec3a4081d6d5fb386c8029
diff --git a/test/unit/checkbox_question_test.rb b/test/unit/checkbox_question_test.rb
index 67974351fa7..551f146d330 100644
--- a/test/unit/checkbox_question_test.rb
+++ b/test/unit/checkbox_question_test.rb
@@ -93,19 +93,5 @@ class CheckboxQuestionTest < ActiveSupport::TestCase
         assert_equal [], @question.to_response('none')
       end
     end
-
-    context "predicate helper functions" do
-      setup do
-        @question = Question::Checkbox.new(nil, :something)
-      end
-
-      should "define response_has_all_of" do
-        assert @question.response_has_all_of([]).is_a?(SmartAnswer::Predicate::ResponseHasAllOf)
-      end
-
-      should "define response_is_one_of" do
-        assert @question.response_is_one_of([]).is_a?(SmartAnswer::Predicate::ResponseIsOneOf)
-      end
-    end
   end
 end
diff --git a/test/unit/predicates_test.rb b/test/unit/predicates_test.rb
deleted file mode 100644
index 7d77eebc0b4..00000000000
--- a/test/unit/predicates_test.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-
-require_relative '../test_helper'
-
-module SmartAnswer
-  module Predicate
-    class PredicatesTest < ActiveSupport::TestCase
-      setup do
-        @state = State.new("start")
-      end
-
-      context "Base predicate" do
-        should "take a label as its first argument" do
-          example_label = "my label"
-          assert_equal example_label, SmartAnswer::Predicate::Base.new(example_label).label
-        end
-
-        should "take a callable as its second argument" do
-          @state.calls_received = []
-          callable = ->(state, response) { state.calls_received << response }
-          predicate = SmartAnswer::Predicate::Base.new(nil, callable)
-          predicate.call(@state, "a")
-          assert_equal ['a'], @state.calls_received
-        end
-
-        should "be able to create a logical union of two predicates with shortcut execution" do
-          @state.p1_calls_received = []
-          @state.p2_calls_received = []
-          p1 = SmartAnswer::Predicate::Base.new('p1', ->(state, response) { state.p1_calls_received << response; response == 'a' })
-          p2 = SmartAnswer::Predicate::Base.new('p2', ->(state, response) { state.p2_calls_received << response; response == 'b' })
-          union = p1 | p2
-          assert union.call(@state, 'a')
-          assert_equal ['a'], @state.p1_calls_received
-          assert_equal [], @state.p2_calls_received
-          assert union.call(@state, 'b')
-          assert_equal ['a', 'b'], @state.p1_calls_received
-          assert_equal ['b'], @state.p2_calls_received
-          refute union.call(@state, 'c')
-          assert_equal ['a', 'b', 'c'], @state.p1_calls_received
-          assert_equal ['b', 'c'], @state.p2_calls_received
-        end
-
-        should "combine using | operator" do
-          p1 = SmartAnswer::Predicate::Base.new('p1')
-          p2 = SmartAnswer::Predicate::Base.new('p2')
-          assert_equal "p1 | p2", (p1 | p2).label
-        end
-
-        should "be able to override lable when combining using or()" do
-          p1 = SmartAnswer::Predicate::Base.new('p1')
-          p2 = SmartAnswer::Predicate::Base.new('p2')
-          assert_equal "custom label", (p1.or(p2, "custom label")).label
-        end
-
-        should "be able to create a logical conjunction of two predicates with shortcut execution" do
-          p_true = SmartAnswer::Predicate::Base.new('true', ->(_, _) { true })
-          p_false = SmartAnswer::Predicate::Base.new('true', ->(_, _) { false })
-
-          assert_equal true, (p_true & p_true).call(@state, 'any input')
-          assert_equal false, (p_true & p_false).call(@state, 'any input')
-          assert_equal false, (p_false & p_true).call(@state, 'any input')
-          assert_equal false, (p_false & p_false).call(@state, 'any input')
-        end
-
-        should "predicate formed by logical conjunction has meaningful label" do
-          p1 = SmartAnswer::Predicate::Base.new('p1', ->(_, _) { true })
-          p2 = SmartAnswer::Predicate::Base.new('p2', ->(_, _) { true })
-
-          assert_equal "p1 AND p2", (p1 & p2).label
-        end
-
-        should "be able to override lable when combining using and()" do
-          p1 = SmartAnswer::Predicate::Base.new('p1')
-          p2 = SmartAnswer::Predicate::Base.new('p2')
-          assert_equal "custom label", (p1.and(p2, "custom label")).label
-        end
-      end
-
-      context "Callable predicate" do
-        should "take a label as its first argument" do
-          example_label = "my label"
-          assert_equal example_label, Callable.new(example_label).label
-        end
-
-        should "take a block" do
-          call_count = 0
-          predicate = Callable.new { call_count += 1 }
-          predicate.call(@state, nil)
-          assert_equal 1, call_count
-        end
-
-        context "take a lambda" do
-          should "accept a lambda with arity of 1" do
-            call_count = 0
-            predicate = Callable.new("label", ->(_) { call_count += 1 })
-            predicate.call(@state, nil)
-            assert_equal 1, call_count
-          end
-
-          should "accept a lambda with arity of 0" do
-            call_count = 0
-            predicate = Callable.new("label", -> { call_count += 1 })
-            predicate.call(@state, nil)
-            assert_equal 1, call_count
-          end
-        end
-
-        should "evaluate its callable in the context of the given state" do
-          @state.calls_received = []
-          predicate = Callable.new(nil, ->(response) { calls_received << response; calls_received.size == 1 })
-          assert predicate.call(@state, 'response1')
-          refute predicate.call(@state, 'response2')
-          assert_equal ['response1', 'response2'], @state.calls_received
-        end
-      end
-
-      context "RespondedWith predicate" do
-        setup do
-          @predicate = RespondedWith.new(%w{a b c})
-        end
-
-        should "check whether the response was one of the permitted options" do
-          assert @predicate.call(@state, 'a')
-          refute @predicate.call(@state, 'd')
-        end
-
-        should "make label from the alternation of options" do
-          assert_equal "a | b | c", @predicate.label
-        end
-      end
-
-      context "ResponseHasAllOf predicate" do
-        should "return true if all responses met" do
-          predicate = ResponseHasAllOf.new(%w{red green})
-          refute predicate.call(@state, '')
-          refute predicate.call(@state, 'red')
-          refute predicate.call(@state, 'blue')
-          refute predicate.call(@state, 'green')
-          refute predicate.call(@state, 'red,blue')
-          assert predicate.call(@state, 'red,green')
-          refute predicate.call(@state, 'blue,green')
-          assert predicate.call(@state, 'red,blue,green')
-        end
-
-        should "always be true if empty requirements" do
-          predicate = ResponseHasAllOf.new([])
-          assert predicate.call(@state, '')
-          assert predicate.call(@state, 'red')
-        end
-
-        should "make label from options" do
-          predicate = ResponseHasAllOf.new(%w{red green})
-          assert_equal "red & green", predicate.label
-        end
-      end
-
-      context "ResponseIsOneOf predicate" do
-        setup do
-          @predicate = ResponseIsOneOf.new(%w{a b c})
-        end
-
-        should "splits the response on comma and indicates if any of the options present" do
-          assert @predicate.call(@state, 'a,b,c')
-          assert @predicate.call(@state, 'a,c,d')
-          assert @predicate.call(@state, 'a')
-          refute @predicate.call(@state, 'd,e,f')
-        end
-
-        should "make label from options" do
-          assert_equal "a | b | c", @predicate.label
-        end
-      end
-
-      context "VariableMatches predicate" do
-        setup do
-          @predicate = VariableMatches.new(:my_var, %w{a b})
-        end
-
-        should "tests if the named variable matches any of the values" do
-          @state.my_var = 'a'
-          assert @predicate.call(@state, '')
-          @state.my_var = 'c'
-          refute @predicate.call(@state, '')
-        end
-
-        should "make label from options" do
-          assert_equal "my_var == { a | b }", @predicate.label
-        end
-
-        should "generate nice label when combining" do
-          p1 = VariableMatches.new(:a, 1, "one")
-          p2 = VariableMatches.new(:a, 2, "two")
-          assert_equal "a == one | two", (p1 | p2).label
-        end
-      end
-    end
-  end
-end
diff --git a/test/unit/question_base_test.rb b/test/unit/question_base_test.rb
index 8443194a4c8..2a0ab341541 100644
--- a/test/unit/question_base_test.rb
+++ b/test/unit/question_base_test.rb
@@ -153,10 +153,9 @@ class QuestionBaseTest < ActiveSupport::TestCase
       next_node_calculation :blah do
         "blah"
       end
-      next_node_if(:done) {
-        complementary_colour == :green
-      }
-      next_node(:shouldnt_go_here)
+      next_node(permitted: [:done]) do
+        :done if complementary_colour == :green
+      end
     end
     initial_state = SmartAnswer::State.new(q.name)
     new_state = q.transition(initial_state, :red)
@@ -165,93 +164,13 @@ class QuestionBaseTest < ActiveSupport::TestCase
     assert_equal :done, new_state.current_node
   end
 
-  test "conditional next node can be specified using next_node_if passing a block" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:bar) { true }
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :bar, q.next_node_for(initial_state, :red)
-  end
-
-  test "conditional next node can be specified using next_node_if passing a predicate" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:bar, ->(_) {true})
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :bar, q.next_node_for(initial_state, :red)
-  end
-
-  test "conditional next node can be specified using next_node_if passing a list of predicates all of which must be true" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:bar, ->(_) {true}, ->(_) {false})
-      next_node_if(:baz, ->(_) {true})
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :baz, q.next_node_for(initial_state, :red)
-  end
-
-  test "conditional next nodes are evaluated in order" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:skipped) { false }
-      next_node_if(:first) { true }
-      next_node_if(:second) { true }
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :first, q.next_node_for(initial_state, :red)
-  end
-
-  test "conditional block is passed input and can refer to state" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:is_red) do |input|
-        color == input
-      end
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    initial_state.color = :red
-    assert_equal :is_red, q.next_node_for(initial_state, :red)
-  end
-
-  test "next_node block used as fallback" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:skipped) { false }
-      next_node(permitted: [:next]) { :next }
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :next, q.next_node_for(initial_state, :red)
-  end
-
-  test "conditional next_node used if triggered ignoring fallback" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:next) { true }
-      next_node(permitted: [:ignored]) { :ignored }
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :next, q.next_node_for(initial_state, :red)
-  end
-
-  test "next_node value used as fallback" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:skipped) { false }
-      next_node(:next)
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :next, q.next_node_for(initial_state, :red)
-  end
-
-  test "next_node value ignored if conditional fires" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      next_node_if(:next) { true }
-      next_node(:ignored)
-    }
-    initial_state = SmartAnswer::State.new(q.name)
-    assert_equal :next, q.next_node_for(initial_state, :red)
-  end
-
   test "error if no conditional transitions found and no fallback" do
     question_name = :example
     responses = [:blue, :red]
     q = SmartAnswer::Question::Base.new(nil, question_name) {
-      next_node_if(:skipped) { false }
+      next_node(permitted: [:skipped]) do
+        :skipped if false
+      end
     }
     initial_state = SmartAnswer::State.new(q.name)
     initial_state.responses << responses[0]
@@ -261,31 +180,4 @@ class QuestionBaseTest < ActiveSupport::TestCase
     expected_message = "Next node undefined. Node: #{question_name}. Responses: #{responses}"
     assert_equal expected_message, error.message
   end
-
-  test "can define a predicate on a node" do
-    q = SmartAnswer::Question::Base.new(nil, :example) {
-      define_predicate(:response_red?) { |response| response == 'red' }
-      define_predicate(:color_red?) { color == 'red' }
-    }
-
-    assert q.response_red?.is_a?(SmartAnswer::Predicate::Callable)
-    assert q.color_red?.is_a?(SmartAnswer::Predicate::Callable)
-
-    state = SmartAnswer::State.new(q.name)
-    state.color = nil
-    assert q.response_red?.call(state, "red")
-    refute q.response_red?.call(state, "green")
-
-    refute q.color_red?.call(state, "")
-    state.color = 'red'
-    assert q.color_red?.call(state, "")
-  end
-
-  test "may not define a predicate for a method which already exists" do
-    assert_raises RuntimeError, /method define_predicate already defined/ do
-      SmartAnswer::Question::Base.new(nil, :example) {
-        define_predicate(:define_predicate) {}
-      }
-    end
-  end
 end