File: view_rendering.rb

package info (click to toggle)
ruby-rspec-rails 8.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,804 kB
  • sloc: ruby: 10,881; sh: 198; makefile: 6
file content (166 lines) | stat: -rw-r--r-- 4,649 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require 'action_view/testing/resolvers'

module RSpec
  module Rails
    # @api public
    # Helpers for optionally rendering views in controller specs.
    module ViewRendering
      extend ActiveSupport::Concern

      # @!attribute [r]
      # Returns the controller object instance under test.
      attr_reader :controller

      # @private
      attr_writer :controller
      private :controller=

      # DSL methods
      module ClassMethods
        # @see RSpec::Rails::ControllerExampleGroup
        def render_views(true_or_false = true)
          @render_views = true_or_false
        end

        # @api private
        def render_views?
          return @render_views if defined?(@render_views)

          if superclass.respond_to?(:render_views?)
            superclass.render_views?
          else
            RSpec.configuration.render_views?
          end
        end
      end

      # @api private
      def render_views?
        self.class.render_views? || !controller.class.respond_to?(:view_paths)
      end

      # @private
      class EmptyTemplateResolver
        def self.build(path)
          if path.is_a?(::ActionView::Resolver)
            ResolverDecorator.new(path)
          else
            FileSystemResolver.new(path)
          end
        end

        def self.nullify_template_rendering(templates)
          templates.map do |template|
            ::ActionView::Template.new(
              "",
              template.identifier,
              EmptyTemplateHandler,
              virtual_path: template.virtual_path,
              format: template_format(template),
              locals: []
            )
          end
        end

        def self.template_format(template)
          template.format
        end

        # Delegates all methods to the submitted resolver and for all methods
        # that return a collection of `ActionView::Template` instances, return
        # templates with modified source
        #
        # @private
        class ResolverDecorator < ::ActionView::Resolver
          (::ActionView::Resolver.instance_methods - Object.instance_methods).each do |method|
            undef_method method
          end

          (::ActionView::Resolver.methods - Object.methods).each do |method|
            singleton_class.undef_method method
          end

          def initialize(resolver)
            @resolver = resolver
          end

          def method_missing(name, *args, &block)
            result = @resolver.send(name, *args, &block)
            nullify_templates(result)
          end

        private

          def nullify_templates(collection)
            return collection unless collection.is_a?(Enumerable)
            return collection unless collection.all? { |element| element.is_a?(::ActionView::Template) }

            EmptyTemplateResolver.nullify_template_rendering(collection)
          end
        end

        # Delegates find_templates to the submitted path set and then returns
        # templates with modified source
        #
        # @private
        class FileSystemResolver < ::ActionView::FileSystemResolver
          private

          def find_templates(*args)
            templates = super
            EmptyTemplateResolver.nullify_template_rendering(templates)
          end
        end
      end

      # @private
      class EmptyTemplateHandler
        def self.call(_template, _source = nil)
          ::Rails.logger.info("  Template rendering was prevented by rspec-rails. Use `render_views` to verify rendered view contents if necessary.")

          %("")
        end
      end

      # Used to null out view rendering in controller specs.
      #
      # @private
      module EmptyTemplates
        def prepend_view_path(new_path)
          super(_path_decorator(*new_path))
        end

        def append_view_path(new_path)
          super(_path_decorator(*new_path))
        end

      private

        def _path_decorator(*paths)
          paths.map { |path| EmptyTemplateResolver.build(path) }
        end
      end

      # @private
      RESOLVER_CACHE = Hash.new do |hash, path|
        hash[path] = EmptyTemplateResolver.build(path)
      end

      included do
        before do
          unless render_views?
            @_original_path_set = controller.class.view_paths
            path_set = @_original_path_set.map { |resolver| RESOLVER_CACHE[resolver] }

            controller.class.view_paths = path_set
            controller.extend(EmptyTemplates)
          end
        end

        after do
          controller.class.view_paths = @_original_path_set unless render_views?
        end
      end
    end
  end
end