rspec-expectations 3.5.0 → 3.9.0
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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +138 -2
- data/README.md +37 -20
- data/lib/rspec/expectations.rb +2 -1
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +14 -0
- data/lib/rspec/expectations/expectation_target.rb +2 -2
- data/lib/rspec/expectations/fail_with.rb +9 -1
- data/lib/rspec/expectations/handler.rb +2 -2
- data/lib/rspec/expectations/minitest_integration.rb +1 -1
- data/lib/rspec/expectations/syntax.rb +2 -2
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +97 -97
- data/lib/rspec/matchers/built_in/all.rb +1 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
- data/lib/rspec/matchers/built_in/be.rb +2 -2
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/change.rb +127 -53
- data/lib/rspec/matchers/built_in/compound.rb +6 -2
- data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
- data/lib/rspec/matchers/built_in/exist.rb +5 -1
- data/lib/rspec/matchers/built_in/has.rb +1 -1
- data/lib/rspec/matchers/built_in/include.rb +6 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
- data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
- data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
- data/lib/rspec/matchers/built_in/yield.rb +43 -30
- data/lib/rspec/matchers/composable.rb +6 -20
- data/lib/rspec/matchers/dsl.rb +72 -4
- data/lib/rspec/matchers/english_phrasing.rb +3 -3
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- metadata +22 -18
- metadata.gz.sig +0 -0
@@ -77,7 +77,11 @@ module RSpec
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def predicates
|
80
|
-
@predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
|
80
|
+
@predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def deprecated(predicate, actual)
|
84
|
+
predicate == :exists? && File == actual
|
81
85
|
end
|
82
86
|
end
|
83
87
|
end
|
@@ -15,12 +15,14 @@ module RSpec
|
|
15
15
|
# @api private
|
16
16
|
# @return [Boolean]
|
17
17
|
def matches?(actual)
|
18
|
+
actual = actual.to_hash if convert_to_hash?(actual)
|
18
19
|
perform_match(actual) { |v| v }
|
19
20
|
end
|
20
21
|
|
21
22
|
# @api private
|
22
23
|
# @return [Boolean]
|
23
24
|
def does_not_match?(actual)
|
25
|
+
actual = actual.to_hash if convert_to_hash?(actual)
|
24
26
|
perform_match(actual) { |v| !v }
|
25
27
|
end
|
26
28
|
|
@@ -137,6 +139,10 @@ module RSpec
|
|
137
139
|
actual.include?(str) && lines.none? { |line| line == str }
|
138
140
|
end
|
139
141
|
end
|
142
|
+
|
143
|
+
def convert_to_hash?(obj)
|
144
|
+
!obj.respond_to?(:include?) && obj.respond_to?(:to_hash)
|
145
|
+
end
|
140
146
|
end
|
141
147
|
end
|
142
148
|
end
|
@@ -172,7 +172,7 @@ module RSpec
|
|
172
172
|
"including those raised by Ruby (e.g. NoMethodError, NameError " \
|
173
173
|
"and ArgumentError), meaning the code you are intending to test " \
|
174
174
|
"may not even get reached. Instead consider using " \
|
175
|
-
"`expect {}.not_to raise_error` or `expect { }.to raise_error" \
|
175
|
+
"`expect { }.not_to raise_error` or `expect { }.to raise_error" \
|
176
176
|
"(DifferentSpecificErrorClass)`. This message can be suppressed by " \
|
177
177
|
"setting: `RSpec::Expectations.configuration.on_potential_false" \
|
178
178
|
"_positives = :nothing`")
|
@@ -125,13 +125,22 @@ module RSpec
|
|
125
125
|
|
126
126
|
return true if expectation.empty?
|
127
127
|
|
128
|
-
|
128
|
+
Support::StrictSignatureVerifier.new(method_signature_for(actual, name)).
|
129
|
+
with_expectation(expectation).valid?
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_signature_for(actual, name)
|
133
|
+
method_handle = Support.method_handle_for(actual, name)
|
129
134
|
|
130
|
-
|
135
|
+
if name == :new && method_handle.owner === ::Class && ::Class === actual
|
136
|
+
Support::MethodSignature.new(actual.instance_method(:initialize))
|
137
|
+
else
|
138
|
+
Support::MethodSignature.new(method_handle)
|
139
|
+
end
|
131
140
|
end
|
132
141
|
|
133
142
|
def with_arity
|
134
|
-
str = ''
|
143
|
+
str = ''.dup
|
135
144
|
str << " with #{with_arity_string}" if @expected_arity
|
136
145
|
str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0
|
137
146
|
str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments
|
@@ -151,7 +160,7 @@ module RSpec
|
|
151
160
|
@expected_keywords.map(&:inspect).join(' and ')
|
152
161
|
else
|
153
162
|
"#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}"
|
154
|
-
|
163
|
+
end
|
155
164
|
|
156
165
|
"keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}"
|
157
166
|
end
|
@@ -5,10 +5,7 @@ module RSpec
|
|
5
5
|
# Provides the implementation for `satisfy`.
|
6
6
|
# Not intended to be instantiated directly.
|
7
7
|
class Satisfy < BaseMatcher
|
8
|
-
|
9
|
-
attr_reader :description
|
10
|
-
|
11
|
-
def initialize(description="satisfy block", &block)
|
8
|
+
def initialize(description=nil, &block)
|
12
9
|
@description = description
|
13
10
|
@block = block
|
14
11
|
end
|
@@ -20,6 +17,11 @@ module RSpec
|
|
20
17
|
@block.call(actual)
|
21
18
|
end
|
22
19
|
|
20
|
+
# @private
|
21
|
+
def description
|
22
|
+
@description ||= "satisfy #{block_representation}"
|
23
|
+
end
|
24
|
+
|
23
25
|
# @api private
|
24
26
|
# @return [String]
|
25
27
|
def failure_message
|
@@ -31,6 +33,27 @@ module RSpec
|
|
31
33
|
def failure_message_when_negated
|
32
34
|
"expected #{actual_formatted} not to #{description}"
|
33
35
|
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
40
|
+
def block_representation
|
41
|
+
if (block_snippet = extract_block_snippet)
|
42
|
+
"expression `#{block_snippet}`"
|
43
|
+
else
|
44
|
+
'block'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_block_snippet
|
49
|
+
return nil unless @block
|
50
|
+
Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def block_representation
|
54
|
+
'block'
|
55
|
+
end
|
56
|
+
end
|
34
57
|
end
|
35
58
|
end
|
36
59
|
end
|
@@ -8,19 +8,17 @@ module RSpec
|
|
8
8
|
# yield matchers is used. Provides information about
|
9
9
|
# the yield behavior of the object-under-test.
|
10
10
|
class YieldProbe
|
11
|
-
def self.probe(block)
|
12
|
-
probe = new(block)
|
11
|
+
def self.probe(block, &callback)
|
12
|
+
probe = new(block, &callback)
|
13
13
|
return probe unless probe.has_block?
|
14
|
-
probe.
|
15
|
-
block.call(probe)
|
16
|
-
probe.assert_used!
|
17
|
-
probe
|
14
|
+
probe.probe
|
18
15
|
end
|
19
16
|
|
20
17
|
attr_accessor :num_yields, :yielded_args
|
21
18
|
|
22
|
-
def initialize(block)
|
19
|
+
def initialize(block, &callback)
|
23
20
|
@block = block
|
21
|
+
@callback = callback || Proc.new {}
|
24
22
|
@used = false
|
25
23
|
self.num_yields = 0
|
26
24
|
self.yielded_args = []
|
@@ -30,13 +28,22 @@ module RSpec
|
|
30
28
|
Proc === @block
|
31
29
|
end
|
32
30
|
|
31
|
+
def probe
|
32
|
+
assert_valid_expect_block!
|
33
|
+
@block.call(self)
|
34
|
+
assert_used!
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
33
38
|
def to_proc
|
34
39
|
@used = true
|
35
40
|
|
36
41
|
probe = self
|
42
|
+
callback = @callback
|
37
43
|
Proc.new do |*args|
|
38
44
|
probe.num_yields += 1
|
39
45
|
probe.yielded_args << args
|
46
|
+
callback.call(*args)
|
40
47
|
nil # to indicate the block does not return a meaningful value
|
41
48
|
end
|
42
49
|
end
|
@@ -56,12 +63,6 @@ module RSpec
|
|
56
63
|
end
|
57
64
|
end
|
58
65
|
|
59
|
-
def successive_yield_args
|
60
|
-
yielded_args.map do |arg_array|
|
61
|
-
arg_array.size == 1 ? arg_array.first : arg_array
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
66
|
def assert_used!
|
66
67
|
return if @used
|
67
68
|
raise 'You must pass the argument yielded to your expect block on ' \
|
@@ -269,10 +270,15 @@ module RSpec
|
|
269
270
|
|
270
271
|
# @private
|
271
272
|
def matches?(block)
|
272
|
-
@
|
273
|
+
@args_matched_when_yielded = true
|
274
|
+
@probe = YieldProbe.new(block) do
|
275
|
+
@actual = @probe.single_yield_args
|
276
|
+
@actual_formatted = actual_formatted
|
277
|
+
@args_matched_when_yielded &&= args_currently_match?
|
278
|
+
end
|
273
279
|
return false unless @probe.has_block?
|
274
|
-
@
|
275
|
-
@probe.yielded_once?(:yield_with_args) &&
|
280
|
+
@probe.probe
|
281
|
+
@probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded
|
276
282
|
end
|
277
283
|
|
278
284
|
# @private
|
@@ -293,7 +299,7 @@ module RSpec
|
|
293
299
|
# @private
|
294
300
|
def description
|
295
301
|
desc = 'yield with args'
|
296
|
-
desc
|
302
|
+
desc = "#{desc}(#{expected_arg_description})" unless @expected.empty?
|
297
303
|
desc
|
298
304
|
end
|
299
305
|
|
@@ -317,16 +323,16 @@ module RSpec
|
|
317
323
|
def negative_failure_reason
|
318
324
|
if !@probe.has_block?
|
319
325
|
'was not a block'
|
320
|
-
elsif
|
326
|
+
elsif @args_matched_when_yielded && !@expected.empty?
|
321
327
|
'yielded with expected arguments' \
|
322
328
|
"\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
|
323
|
-
"\n got: #{actual_formatted}"
|
329
|
+
"\n got: #{@actual_formatted}"
|
324
330
|
else
|
325
331
|
'did'
|
326
332
|
end
|
327
333
|
end
|
328
334
|
|
329
|
-
def
|
335
|
+
def args_currently_match?
|
330
336
|
if @expected.empty? # expect {...}.to yield_with_args
|
331
337
|
@positive_args_failure = 'yielded with no arguments' if @actual.empty?
|
332
338
|
return !@actual.empty?
|
@@ -335,7 +341,7 @@ module RSpec
|
|
335
341
|
unless (match = all_args_match?)
|
336
342
|
@positive_args_failure = 'yielded with unexpected arguments' \
|
337
343
|
"\nexpected: #{surface_descriptions_in(@expected).inspect}" \
|
338
|
-
"\n got: #{actual_formatted}"
|
344
|
+
"\n got: #{@actual_formatted}"
|
339
345
|
end
|
340
346
|
|
341
347
|
match
|
@@ -356,10 +362,21 @@ module RSpec
|
|
356
362
|
|
357
363
|
# @private
|
358
364
|
def matches?(block)
|
359
|
-
@
|
365
|
+
@actual_formatted = []
|
366
|
+
@actual = []
|
367
|
+
args_matched_when_yielded = true
|
368
|
+
yield_count = 0
|
369
|
+
|
370
|
+
@probe = YieldProbe.probe(block) do |*arg_array|
|
371
|
+
arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array
|
372
|
+
@actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args)
|
373
|
+
@actual << arg_or_args
|
374
|
+
args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args)
|
375
|
+
yield_count += 1
|
376
|
+
end
|
377
|
+
|
360
378
|
return false unless @probe.has_block?
|
361
|
-
|
362
|
-
args_match?
|
379
|
+
args_matched_when_yielded && yield_count == @expected.length
|
363
380
|
end
|
364
381
|
|
365
382
|
def does_not_match?(block)
|
@@ -390,10 +407,6 @@ module RSpec
|
|
390
407
|
|
391
408
|
private
|
392
409
|
|
393
|
-
def args_match?
|
394
|
-
values_match?(@expected, @actual)
|
395
|
-
end
|
396
|
-
|
397
410
|
def expected_arg_description
|
398
411
|
@expected.map { |e| description_of e }.join(', ')
|
399
412
|
end
|
@@ -403,7 +416,7 @@ module RSpec
|
|
403
416
|
|
404
417
|
'yielded with unexpected arguments' \
|
405
418
|
"\nexpected: #{surface_descriptions_in(@expected).inspect}" \
|
406
|
-
"\n got: #{actual_formatted}"
|
419
|
+
"\n got: [#{@actual_formatted.join(", ")}]"
|
407
420
|
end
|
408
421
|
|
409
422
|
def negative_failure_reason
|
@@ -411,7 +424,7 @@ module RSpec
|
|
411
424
|
|
412
425
|
'yielded with expected arguments' \
|
413
426
|
"\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
|
414
|
-
"\n got: #{actual_formatted}"
|
427
|
+
"\n got: [#{@actual_formatted.join(", ")}]"
|
415
428
|
end
|
416
429
|
end
|
417
430
|
end
|
@@ -130,8 +130,6 @@ module RSpec
|
|
130
130
|
object.clone
|
131
131
|
elsif Hash === object
|
132
132
|
Hash[with_matchers_cloned(object.to_a)]
|
133
|
-
elsif Struct === object || unreadable_io?(object)
|
134
|
-
object
|
135
133
|
elsif should_enumerate?(object)
|
136
134
|
object.map { |subobject| with_matchers_cloned(subobject) }
|
137
135
|
else
|
@@ -139,24 +137,10 @@ module RSpec
|
|
139
137
|
end
|
140
138
|
end
|
141
139
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# 1-character strings, which are themselves enumerable, composed of a
|
147
|
-
# a single 1-character string, which is an enumerable, etc.
|
148
|
-
#
|
149
|
-
# @api private
|
150
|
-
def should_enumerate?(item)
|
151
|
-
return false if String === item
|
152
|
-
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
|
153
|
-
end
|
154
|
-
# :nocov:
|
155
|
-
else
|
156
|
-
# @api private
|
157
|
-
def should_enumerate?(item)
|
158
|
-
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
|
159
|
-
end
|
140
|
+
# @api private
|
141
|
+
# We should enumerate arrays as long as they are not recursive.
|
142
|
+
def should_enumerate?(item)
|
143
|
+
Array === item && item.none? { |subitem| subitem.equal?(item) }
|
160
144
|
end
|
161
145
|
|
162
146
|
# @api private
|
@@ -172,10 +156,12 @@ module RSpec
|
|
172
156
|
# Wraps an item in order to surface its `description` via `inspect`.
|
173
157
|
# @api private
|
174
158
|
DescribableItem = Struct.new(:item) do
|
159
|
+
# Inspectable version of the item description
|
175
160
|
def inspect
|
176
161
|
"(#{item.description})"
|
177
162
|
end
|
178
163
|
|
164
|
+
# A pretty printed version of the item description.
|
179
165
|
def pretty_print(pp)
|
180
166
|
pp.text "(#{item.description})"
|
181
167
|
end
|
data/lib/rspec/matchers/dsl.rb
CHANGED
@@ -2,7 +2,70 @@ module RSpec
|
|
2
2
|
module Matchers
|
3
3
|
# Defines the custom matcher DSL.
|
4
4
|
module DSL
|
5
|
+
# Defines a matcher alias. The returned matcher's `description` will be overriden
|
6
|
+
# to reflect the phrasing of the new name, which will be used in failure messages
|
7
|
+
# when passed as an argument to another matcher in a composed matcher expression.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
|
11
|
+
# sum_to(3).description # => "sum to 3"
|
12
|
+
# a_list_that_sums_to(3).description # => "a list that sums to 3"
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
|
16
|
+
# description.sub("be sorted by", "a list sorted by")
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# be_sorted_by(:age).description # => "be sorted by age"
|
20
|
+
# a_list_sorted_by(:age).description # => "a list sorted by age"
|
21
|
+
#
|
22
|
+
# @param new_name [Symbol] the new name for the matcher
|
23
|
+
# @param old_name [Symbol] the original name for the matcher
|
24
|
+
# @param options [Hash] options for the aliased matcher
|
25
|
+
# @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
|
26
|
+
# @yield [String] optional block that, when given, is used to define the overriden
|
27
|
+
# logic. The yielded arg is the original description or failure message. If no
|
28
|
+
# block is provided, a default override is used based on the old and new names.
|
29
|
+
# @see RSpec::Matchers
|
30
|
+
def alias_matcher(new_name, old_name, options={}, &description_override)
|
31
|
+
description_override ||= lambda do |old_desc|
|
32
|
+
old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
|
33
|
+
end
|
34
|
+
klass = options.fetch(:klass) { AliasedMatcher }
|
35
|
+
|
36
|
+
define_method(new_name) do |*args, &block|
|
37
|
+
matcher = __send__(old_name, *args, &block)
|
38
|
+
matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=)
|
39
|
+
klass.new(matcher, description_override)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Defines a negated matcher. The returned matcher's `description` and `failure_message`
|
44
|
+
# will be overriden to reflect the phrasing of the new name, and the match logic will
|
45
|
+
# be based on the original matcher but negated.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# RSpec::Matchers.define_negated_matcher :exclude, :include
|
49
|
+
# include(1, 2).description # => "include 1 and 2"
|
50
|
+
# exclude(1, 2).description # => "exclude 1 and 2"
|
51
|
+
#
|
52
|
+
# @param negated_name [Symbol] the name for the negated matcher
|
53
|
+
# @param base_name [Symbol] the name of the original matcher that will be negated
|
54
|
+
# @yield [String] optional block that, when given, is used to define the overriden
|
55
|
+
# logic. The yielded arg is the original description or failure message. If no
|
56
|
+
# block is provided, a default override is used based on the old and new names.
|
57
|
+
# @see RSpec::Matchers
|
58
|
+
def define_negated_matcher(negated_name, base_name, &description_override)
|
59
|
+
alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
|
60
|
+
end
|
61
|
+
|
5
62
|
# Defines a custom matcher.
|
63
|
+
#
|
64
|
+
# @param name [Symbol] the name for the matcher
|
65
|
+
# @yield [Object] block that is used to define the matcher.
|
66
|
+
# The block is evaluated in the context of your custom matcher class.
|
67
|
+
# When args are passed to your matcher, they will be yielded here,
|
68
|
+
# usually representing the expected value(s).
|
6
69
|
# @see RSpec::Matchers
|
7
70
|
def define(name, &declarations)
|
8
71
|
warn_about_block_args(name, declarations)
|
@@ -84,8 +147,14 @@ module RSpec
|
|
84
147
|
# is rarely necessary, but can be helpful, for example, when specifying
|
85
148
|
# asynchronous processes that require different timeouts.
|
86
149
|
#
|
150
|
+
# By default the match block will swallow expectation errors (e.g.
|
151
|
+
# caused by using an expectation such as `expect(1).to eq 2`), if you
|
152
|
+
# with to allow these to bubble up, pass in the option
|
153
|
+
# `:notify_expectation_failures => true`.
|
154
|
+
#
|
155
|
+
# @param [Hash] options for defining the behavior of the match block.
|
87
156
|
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
|
88
|
-
def match_when_negated(&match_block)
|
157
|
+
def match_when_negated(options={}, &match_block)
|
89
158
|
define_user_override(:does_not_match?, match_block) do |actual|
|
90
159
|
begin
|
91
160
|
@actual = actual
|
@@ -93,6 +162,7 @@ module RSpec
|
|
93
162
|
super(*actual_arg_for(match_block))
|
94
163
|
end
|
95
164
|
rescue RSpec::Expectations::ExpectationNotMetError
|
165
|
+
raise if options[:notify_expectation_failures]
|
96
166
|
false
|
97
167
|
end
|
98
168
|
end
|
@@ -266,7 +336,7 @@ module RSpec
|
|
266
336
|
#
|
267
337
|
# This compiles the user block into an actual method, allowing
|
268
338
|
# them to use normal method constructs like `return`
|
269
|
-
# (e.g. for
|
339
|
+
# (e.g. for an early guard statement), while allowing us to define
|
270
340
|
# an override that can provide the wrapped handling
|
271
341
|
# (e.g. assigning `@actual`, rescueing errors, etc) and
|
272
342
|
# can `super` to the user's definition.
|
@@ -462,5 +532,3 @@ module RSpec
|
|
462
532
|
end
|
463
533
|
end
|
464
534
|
end
|
465
|
-
|
466
|
-
RSpec::Matchers.extend RSpec::Matchers::DSL
|