line-message-builder 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37235cb021f9418afaad94d10737c24c596203d412f9bccf8024caaec829bc2e
4
- data.tar.gz: 0fb0fd5faf9b3c56a8c2a08d4dc72f39fa36b540a8e5f72f57358a392b7efe11
3
+ metadata.gz: f66fccfc1d5cd52c32662d8e912aac28f79e82d0b86ee837125ebe292fdd5f13
4
+ data.tar.gz: b819400dc6c6afbcb85b99335dbbf60c0a8c2cfe71c4f4f2aa3653e56fd5edf5
5
5
  SHA512:
6
- metadata.gz: aac9d97a2edb4d90059f76d8716d768af138dbb411d65f3d16057da227be00ab6b8dd26af09ac96c4ee476f8b30343922b6294e9e4956155ad38f237b5fa4020
7
- data.tar.gz: fedb66cb7216b45ba2afebe70b9acb5fd1a7fa428f9b2508601229239ac5bd4004e842324432b787dd752474805a68d9e40354b02170352cbbf3d2c4aa80d5e0
6
+ metadata.gz: 2e7a7f40dede7c209dd9d4f581020a2d9cf9b9403675b055382446a440852861ab1585832b9bae31b31a12aaea68f5bd7c32e2ce9cde8950715db38864b726dd
7
+ data.tar.gz: 87d0a63d6fdab6a7338745eac2e3b391605c8631ac7cef4226ac17e098b93ac152f39ecfe5bbfbe9f98576ab52fd92e9a1377819c3bfc10238cd95e342ff09fb
@@ -0,0 +1 @@
1
+ {".":"0.2.0"}
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0](https://github.com/elct9620/line-message-builder/compare/v0.1.0...v0.2.0) (2025-04-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add Flex button component with message and postback actions ([0fe6ecb](https://github.com/elct9620/line-message-builder/commit/0fe6ecb4cdf17e180c4ca0230a810a6d62bead9b))
9
+ * Add Flex message builder support to Line message builder ([2f8e191](https://github.com/elct9620/line-message-builder/commit/2f8e191d466e94763b66829520e3f6de3bc83be7))
10
+ * Add Flex message components for Line messaging with Box, Bubble, and Text support ([92c2c14](https://github.com/elct9620/line-message-builder/commit/92c2c14be36307993452d1705110c8ffecd162da))
11
+ * Add image method to Flex::Box for creating flex images ([b111f67](https://github.com/elct9620/line-message-builder/commit/b111f67c50777855df09cf2fc7441dea21b13c5b))
12
+ * Add nested box support in Flex message builder ([b93ea98](https://github.com/elct9620/line-message-builder/commit/b93ea98130de3011bebbc9c216fbfa1de35c8270))
13
+ * Add optional parameters to Flex Bubble component methods ([5e37949](https://github.com/elct9620/line-message-builder/commit/5e379492c6235756e9c3e0ce2489da2202e59e45))
14
+ * Add quick reply support to Line message builder with RSpec matchers ([3ea78af](https://github.com/elct9620/line-message-builder/commit/3ea78af7cb0f4334e6d36b6b1567405ef6eb27f4))
15
+ * Add spec for Line flex text with various text attributes ([8501fb7](https://github.com/elct9620/line-message-builder/commit/8501fb7803bf679697034b9d113714557eaea6c3))
16
+ * Add support for alt_text in Flex message builder and RSpec matcher ([8b64ba4](https://github.com/elct9620/line-message-builder/commit/8b64ba4566245d10e95aaf88ed303c69f7eeecb2))
17
+ * Add support for button style and height in flex message matchers ([8de297d](https://github.com/elct9620/line-message-builder/commit/8de297d88a1d4276d6ceb221777b006cccb71257))
18
+ * Add support for hero images in Line Flex messages ([d776264](https://github.com/elct9620/line-message-builder/commit/d776264e6e276bd4048362ff3cdb6cc9dda91643))
data/README.md CHANGED
@@ -24,7 +24,7 @@ gem install line-message-builder
24
24
  ### Builder
25
25
 
26
26
  ```ruby
27
- builder = Line::MessageBuilder::Builder.new do
27
+ builder = Line::MessageBuilder::Builder.with do
28
28
  text "Hello, world!"
29
29
  end
30
30
 
@@ -44,7 +44,7 @@ context = OpenStruct.new(
44
44
  name: "John Doe",
45
45
  )
46
46
 
47
- builder = Line::MessageBuilder::Builder.new(context) do
47
+ builder = Line::MessageBuilder::Builder.with(context) do
48
48
  text "Hello, #{name}!"
49
49
  end
50
50
 
@@ -59,7 +59,7 @@ For Rails, you can use `view_context` to make `Builder` to access Rails helpers.
59
59
 
60
60
  ```ruby
61
61
  # app/controllers/line_controller.rb
62
- builder = Line::MessageBuilder::Builder.new(view_context) do
62
+ builder = Line::MessageBuilder::Builder.with(view_context) do
63
63
  text "Anything you want?" do
64
64
  quick_reply do
65
65
  action "Yes", label: "Yes", image_url: image_url("yes.png")
@@ -79,18 +79,54 @@ context = ActionView::Base.new(
79
79
  ActionController::Base.new,
80
80
  )
81
81
 
82
- builder = Line::MessageBuilder::Builder.new(context) do
82
+ builder = Line::MessageBuilder::Builder.with(context) do
83
83
  text "Anything you want?" do
84
84
  quick_reply do
85
- action "Yes", label: "Yes", image_url: context.image_url("yes.png")
86
- action "No", label: "No", image_url: context.image_url("no.png")
85
+ action "Yes", label: "Yes", image_url: image_url("yes.png")
86
+ action "No", label: "No", image_url: image_url("no.png")
87
87
  end
88
88
  end
89
89
  end
90
90
  ```
91
91
 
92
+ ### RSpec Matcher
93
+
94
+ Add `line/message/rspec` to your `spec_helper.rb` or `rails_helper.rb`:
95
+
96
+ ```ruby
97
+ require "line/message/rspec"
98
+ ```
99
+
100
+ Include `Line::Message::RSpec::Matchers` in your RSpec configuration:
101
+
102
+ ```ruby
103
+ RSpec.configure do |config|
104
+ config.include Line::Message::RSpec::Matchers
105
+ end
106
+ ```
107
+
108
+ Then the matchers are available in your specs:
109
+
110
+ ```ruby
111
+ let(:builder) do
112
+ Line::MessageBuilder::Builder.with do
113
+ text "Hello, world!"
114
+ text "Nice to meet you!"
115
+ end
116
+ end
117
+
118
+ subject { builder.build }
119
+
120
+ it { is_expected.to have_line_text_message("Hello, world!") }
121
+ it { is_expected.to have_line_text_message(/Nice to meet you!/) }
122
+ ```
123
+
92
124
  ## Capabilities
93
125
 
126
+ - ✅ Supported
127
+ - 🚧 Partially Supported
128
+ - ❌ Not Supported
129
+
94
130
  ### Message Types
95
131
 
96
132
  | Type | Supported |
@@ -105,13 +141,20 @@ end
105
141
  | Location | ❌ |
106
142
  | Imagemap | ❌ |
107
143
  | Template | ❌ |
108
- | Flex | |
144
+ | Flex | 🚧 |
145
+
146
+ ### Common Properties
147
+
148
+ | Property | Supported |
149
+ | -------- | --------- |
150
+ | Quick Reply | ✅ |
151
+ | Sender | ❌ |
109
152
 
110
153
  ### Actions
111
154
 
112
155
  | Action Type | Supported |
113
156
  | ----------- | --------- |
114
- | Postback | |
157
+ | Postback | 🚧 |
115
158
  | Message | ✅ |
116
159
  | Uri | ❌ |
117
160
  | Datetime | ❌ |
@@ -121,6 +164,22 @@ end
121
164
  | Richmenu Switch | ❌ |
122
165
  | Clipboard | ❌ |
123
166
 
167
+ ### Flex Components
168
+
169
+ | Component | Supported |
170
+ | --------- | --------- |
171
+ | Bubble | 🚧 |
172
+ | Carousel | ❌ |
173
+ | Box | 🚧 |
174
+ | Button | 🚧 |
175
+ | Image | 🚧 |
176
+ | Video | ❌ |
177
+ | Icon | ❌ |
178
+ | Text | 🚧 |
179
+ | Span | ❌ |
180
+ | Separator | ❌ |
181
+ | Filler | ❌ |
182
+
124
183
  ## Development
125
184
 
126
185
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  module Actions
7
7
  # The Message class is used to build message actions for quick replies.
8
8
  class Message < Line::Message::Builder::Base
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  module Actions
7
7
  # The Postback class is used to build postback actions for quick replies.
8
8
  class Postback < Line::Message::Builder::Base
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  # The actions module contains classes for building different types of actions
7
7
  module Actions
8
8
  require_relative "actions/message"
@@ -2,17 +2,52 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  # The base class to provide DSL functionality.
7
7
  class Base
8
+ class << self
9
+ def inherited(subclass)
10
+ super
11
+ subclass.extend ClassMethods
12
+ end
13
+ end
14
+
15
+ # :nodoc:
16
+ module ClassMethods
17
+ def options
18
+ @options ||= []
19
+ end
20
+
21
+ def option(name, default: nil)
22
+ options << name
23
+
24
+ define_method name do |*args|
25
+ if args.empty?
26
+ instance_variable_get("@#{name}") || default
27
+ else
28
+ instance_variable_set("@#{name}", args.first)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
8
34
  attr_reader :context
9
35
 
10
- def initialize(context: nil, &block)
36
+ def initialize(context: nil, **options, &block)
11
37
  @context = context
38
+ @quick_reply = nil
39
+
40
+ self.class.options.each do |option|
41
+ instance_variable_set("@#{option}", options[option]) if options.key?(option)
42
+ end
12
43
 
13
44
  instance_eval(&block) if ::Kernel.block_given?
14
45
  end
15
46
 
47
+ def quick_reply(&)
48
+ @quick_reply = QuickReply.new(context: context, &)
49
+ end
50
+
16
51
  def respond_to_missing?(method_name, include_private = false)
17
52
  context.respond_to?(method_name, include_private) || super
18
53
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ # The container class is main container to manage messages.
7
+ class Container < Base
8
+ attr_reader :context
9
+
10
+ def initialize(context: nil, &)
11
+ @messages = []
12
+
13
+ super
14
+ end
15
+
16
+ def text(text, &)
17
+ @messages << Text.new(text, context: context, &)
18
+ end
19
+
20
+ def flex(**args, &)
21
+ @messages << Flex::Builder.new(**args, context: context, &)
22
+ end
23
+
24
+ def build
25
+ @messages.map(&:to_h)
26
+ end
27
+
28
+ def to_json(*args)
29
+ build.to_json(*args)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The box is a component for the Flex message.
8
+ class Box < Line::Message::Builder::Base
9
+ option :layout, default: :horizontal
10
+
11
+ def initialize(context: nil, **options, &)
12
+ @contents = []
13
+
14
+ super
15
+ end
16
+
17
+ def box(**options, &)
18
+ @contents << Flex::Box.new(context: context, **options, &)
19
+ end
20
+
21
+ def text(text, **options, &)
22
+ @contents << Flex::Text.new(text, context: context, **options, &)
23
+ end
24
+
25
+ def button(**options, &)
26
+ @contents << Flex::Button.new(context: context, **options, &)
27
+ end
28
+
29
+ def image(url, **options, &)
30
+ @contents << Flex::Image.new(url, context: context, **options, &)
31
+ end
32
+
33
+ def to_h
34
+ {
35
+ type: "box",
36
+ layout: @layout,
37
+ contents: @contents.map(&:to_h)
38
+ }.compact
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The bubble is container for the Flex message.
8
+ class Bubble < Line::Message::Builder::Base
9
+ def initialize(context: nil, &)
10
+ @header = nil
11
+ @hero = nil
12
+ @body = nil
13
+ @footer = nil
14
+
15
+ super
16
+ end
17
+
18
+ def header(**options, &)
19
+ @header = Box.new(**options, context: context, &)
20
+ end
21
+
22
+ def hero(**options, &)
23
+ @hero = Box.new(**options, context: context, &)
24
+ end
25
+
26
+ def hero_image(url, **options, &)
27
+ @hero = Image.new(url, **options, context: context, &)
28
+ end
29
+
30
+ def body(**options, &)
31
+ @body = Box.new(**options, context: context, &)
32
+ end
33
+
34
+ def footer(**options, &)
35
+ @footer = Box.new(**options, context: context, &)
36
+ end
37
+
38
+ def to_h
39
+ {
40
+ type: "bubble",
41
+ header: @header&.to_h,
42
+ hero: @hero&.to_h,
43
+ body: @body&.to_h,
44
+ footer: @footer&.to_h
45
+ }.compact
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The Builder class is used to build quick reply buttons.
8
+ class Builder < Line::Message::Builder::Base
9
+ option :alt_text, default: nil
10
+
11
+ def initialize(context: nil, **options, &)
12
+ @contents = []
13
+
14
+ super
15
+ end
16
+
17
+ def bubble(&)
18
+ @contents << Line::Message::Builder::Flex::Bubble.new(context: context, &)
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ type: "flex",
24
+ altText: @alt_text,
25
+ contents: @contents.map(&:to_h),
26
+ quickReply: @quick_reply&.to_h
27
+ }.compact
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The button is a component of the Flex message.
8
+ class Button < Line::Message::Builder::Base
9
+ option :style, default: :link
10
+ option :height, default: :md
11
+
12
+ def initialize(context: nil, **options, &)
13
+ @action = nil
14
+
15
+ super
16
+ end
17
+
18
+ def message(text, label:, display_text: nil, &)
19
+ @action = Actions::Message.new(text: text, label: label, display_text: display_text, &)
20
+ end
21
+
22
+ def postback(data, label: nil, display_text: nil, &)
23
+ @action = Actions::Postback.new(data: data, label: label, display_text: display_text, &)
24
+ end
25
+
26
+ def to_h
27
+ raise Error, "Action is required" unless @action
28
+
29
+ {
30
+ type: "button",
31
+ action: @action.to_h,
32
+ style: @style,
33
+ height: @height
34
+ }.compact
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The image is a component for the Flex message.
8
+ class Image < Line::Message::Builder::Base
9
+ option :size, default: nil
10
+ option :aspect_ratio, default: nil
11
+ option :aspect_mode, default: nil
12
+
13
+ def initialize(url, context: nil, **options, &)
14
+ @url = url
15
+
16
+ super(context: context, **options, &)
17
+ end
18
+
19
+ def size(size)
20
+ @size = size
21
+ end
22
+
23
+ def aspect_ratio(aspect_ratio)
24
+ @aspect_ratio = aspect_ratio
25
+ end
26
+
27
+ def aspect_mode(aspect_mode)
28
+ @aspect_mode = aspect_mode
29
+ end
30
+
31
+ def to_h
32
+ {
33
+ type: "image",
34
+ url: @url,
35
+ size: @size,
36
+ aspectRatio: @aspect_ratio,
37
+ aspectMode: @aspect_mode
38
+ }.compact
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The text is a component of the Flex message.
8
+ class Text < Line::Message::Builder::Base
9
+ option :wrap, default: false
10
+ option :line_spacing, default: nil
11
+ option :color, default: nil
12
+ option :size, default: nil
13
+ option :align, default: nil
14
+ option :flex, default: nil
15
+
16
+ def initialize(text, context: nil, **options, &)
17
+ @text = text
18
+
19
+ super(context: context, **options, &)
20
+ end
21
+
22
+ def wrap!
23
+ @wrap = true
24
+ end
25
+
26
+ def to_h
27
+ {
28
+ type: "text",
29
+ text: @text,
30
+ wrap: @wrap,
31
+ lineSpacing: @line_spacing,
32
+ color: @color,
33
+ size: @size,
34
+ align: @align,
35
+ flex: @flex
36
+ }.compact
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ # The Flex module allows to build Flex messages.
7
+ module Flex
8
+ require_relative "flex/builder"
9
+
10
+ # Container
11
+ require_relative "flex/bubble"
12
+
13
+ # Components
14
+ require_relative "flex/box"
15
+ require_relative "flex/text"
16
+ require_relative "flex/button"
17
+ require_relative "flex/image"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,13 +2,45 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  # The QuickReply allows to attach quick reply buttons to a message.
7
- module QuickReply
8
- require_relative "quick_reply/builder"
7
+ class QuickReply < Line::Message::Builder::Base
8
+ def initialize(context: nil, &)
9
+ @items = []
9
10
 
10
- def quick_reply(&)
11
- @quick_reply ||= Builder.new(context: context, &)
11
+ super
12
+ end
13
+
14
+ def message(text, label:, image_url: nil, &)
15
+ action(
16
+ Actions::Message.new(text: text, label: label, &),
17
+ image_url
18
+ )
19
+ end
20
+
21
+ def postback(data, label: nil, display_text: nil, image_url: nil, &)
22
+ action(
23
+ Actions::Postback.new(data: data, label: label, display_text: display_text, &),
24
+ image_url
25
+ )
26
+ end
27
+
28
+ def to_h
29
+ {
30
+ items: @items.map do |item, image_url|
31
+ {
32
+ type: "action",
33
+ imageUrl: image_url,
34
+ action: item.to_h
35
+ }
36
+ end
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def action(action, image_url)
43
+ @items << [action, image_url]
12
44
  end
13
45
  end
14
46
  end
@@ -2,11 +2,9 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
5
+ module Builder
6
6
  # Text message builder.
7
7
  class Text < Base
8
- include QuickReply
9
-
10
8
  def initialize(text, context: nil, &block)
11
9
  @text = text
12
10
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Line
4
4
  module Message
5
- class Builder
6
- VERSION = "0.1.0"
5
+ module Builder
6
+ VERSION = "0.2.0"
7
7
  end
8
8
  end
9
9
  end
@@ -5,45 +5,20 @@ require_relative "builder/version"
5
5
  module Line
6
6
  module Message
7
7
  # The Builder module provides a DSL for building LINE messages.
8
- class Builder
8
+ module Builder
9
9
  class Error < StandardError; end
10
10
 
11
11
  require_relative "builder/base"
12
12
  require_relative "builder/actions"
13
13
  require_relative "builder/quick_reply"
14
+ require_relative "builder/container"
14
15
  require_relative "builder/text"
16
+ require_relative "builder/flex"
15
17
 
16
- attr_reader :context
18
+ module_function
17
19
 
18
- def initialize(context = nil, &)
19
- @messages = []
20
- @context = context
21
-
22
- instance_eval(&) if ::Kernel.block_given?
23
- end
24
-
25
- def text(text)
26
- @messages << Text.new(text, context: context)
27
- end
28
-
29
- def build
30
- @messages.map(&:to_h)
31
- end
32
-
33
- def to_json(*args)
34
- build.to_json(*args)
35
- end
36
-
37
- def respond_to_missing?(method_name, include_private = false)
38
- context.respond_to?(method_name, include_private) || super
39
- end
40
-
41
- def method_missing(method_name, ...)
42
- if context.respond_to?(method_name)
43
- context.public_send(method_name, ...)
44
- else
45
- super
46
- end
20
+ def with(context = nil, &)
21
+ Container.new(context: context, &)
47
22
  end
48
23
  end
49
24
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module RSpec
6
+ # :nodoc:
7
+ module Matchers
8
+ # The flex component matcher for RSpec to search nested flex components in the message array.
9
+ class HaveFlexComponent
10
+ def initialize(expected_desc: nil, &expected)
11
+ @expected = expected
12
+ @expected_desc = expected_desc
13
+ end
14
+
15
+ def description
16
+ return "have flex component" if @expected.nil?
17
+ return "have flex component matching #{@expected_desc}" if @expected_desc
18
+
19
+ "have flex component matching #{@expected.inspect}"
20
+ end
21
+
22
+ def matches?(actual)
23
+ @actual = actual
24
+ @actual.any? { |message| match_flex_component?(message) }
25
+ end
26
+
27
+ def failure_message
28
+ return "expected to find a flex component" if @expected.nil?
29
+ return "expected to find a flex component matching #{@expected_desc}" if @expected_desc
30
+
31
+ "expected to find a flex component matching #{@expected.inspect}"
32
+ end
33
+
34
+ private
35
+
36
+ def match_flex_component?(message)
37
+ return false unless message[:type] == "flex"
38
+
39
+ message[:contents].any? { |content| match_content?(content) }
40
+ end
41
+
42
+ def match_content?(content)
43
+ return match_bubble?(content) if content[:type] == "bubble"
44
+
45
+ if content[:contents]
46
+ return content[:contents].any? do |nested_content|
47
+ match_content?(nested_content)
48
+ end || @expected.call(content)
49
+ end
50
+
51
+ @expected.call(content)
52
+ end
53
+
54
+ def match_bubble?(content)
55
+ %i[header hero body footer].any? do |key|
56
+ block = content[key]
57
+ next unless block
58
+
59
+ match_bubble_block?(block)
60
+ end
61
+ end
62
+
63
+ def match_bubble_block?(block)
64
+ return block[:contents].any? { |nested_content| match_content?(nested_content) } if block[:contents]
65
+
66
+ match_content?(block)
67
+ end
68
+ end
69
+
70
+ def have_line_flex_component(&) # rubocop:disable Naming/PredicateName
71
+ HaveFlexComponent.new(&)
72
+ end
73
+
74
+ def have_line_flex_box(**options) # rubocop:disable Naming/PredicateName
75
+ HaveFlexComponent.new(expected_desc: "box(#{options.inspect})") do |content|
76
+ next false unless content[:type] == "box"
77
+
78
+ ::RSpec::Matchers::BuiltIn::Include.new(options).matches?(content)
79
+ end
80
+ end
81
+
82
+ def have_line_flex_text(text, **options) # rubocop:disable Naming/PredicateName
83
+ HaveFlexComponent.new(expected_desc: "text(#{text.inspect})") do |content|
84
+ next false unless content[:type] == "text"
85
+
86
+ content[:text].match?(text) && ::RSpec::Matchers::BuiltIn::Include.new(options).matches?(content)
87
+ end
88
+ end
89
+
90
+ def have_line_flex_button(type, **options) # rubocop:disable Naming/PredicateName
91
+ HaveFlexComponent.new(expected_desc: "#{type} button(#{options.inspect})") do |content|
92
+ next false unless content[:type] == "button"
93
+
94
+ ::RSpec::Matchers::BuiltIn::Include.new({ type: type, **options }).matches?(content[:action]) ||
95
+ ::RSpec::Matchers::BuiltIn::Include.new({ **options, action: ::RSpec::Matchers::BuiltIn::Include.new(
96
+ type: type
97
+ ) }).matches?(content)
98
+ end
99
+ end
100
+
101
+ def have_line_flex_image(url, **options) # rubocop:disable Naming/PredicateName
102
+ HaveFlexComponent.new(expected_desc: "image(#{url.inspect})") do |content|
103
+ next false unless content[:type] == "image"
104
+
105
+ ::RSpec::Matchers::BuiltIn::Include.new({ url: url, **options }).matches?(content)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module RSpec
6
+ # :nodoc:
7
+ module Matchers
8
+ # The flex message matcher for RSpec to search for flex messages in the message array.
9
+ class HaveFlexMessage
10
+ def initialize(expected)
11
+ @expected = expected
12
+ end
13
+
14
+ def description
15
+ return "have flex message" if @expected.nil?
16
+
17
+ "have flex message alt text matching #{@expected.inspect}"
18
+ end
19
+
20
+ def matches?(actual)
21
+ @actual = actual
22
+ @actual.any? { |message| match_alt_text?(message) }
23
+ end
24
+
25
+ def failure_message
26
+ "expected to find a flex message alt text matching #{@expected}"
27
+ end
28
+
29
+ private
30
+
31
+ def match_alt_text?(message)
32
+ return false unless message[:type] == "flex"
33
+ return true if @expected.nil?
34
+
35
+ message[:altText].match?(@expected)
36
+ end
37
+ end
38
+
39
+ def have_line_flex_message(expected = nil) # rubocop:disable Naming/PredicateName
40
+ HaveFlexMessage.new(expected)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module RSpec
6
+ # :nodoc:
7
+ module Matchers
8
+ # The quick reply matcher for RSpec to search for quick reply action in the message array.
9
+ class HaveQuickReply
10
+ def initialize(expected)
11
+ @expected = expected
12
+ end
13
+
14
+ def description
15
+ return "have quick reply action" if @expected.nil?
16
+
17
+ "have quick reply action matching #{@expected}"
18
+ end
19
+
20
+ def matches?(actual)
21
+ @actual = actual
22
+ @actual.any? { |message| match_message(message) }
23
+ end
24
+
25
+ def failure_message
26
+ "expected to find a quick reply message matching #{@expected}"
27
+ end
28
+
29
+ private
30
+
31
+ def match_message(message)
32
+ reply = message[:quickReply]
33
+ return false unless reply
34
+
35
+ reply[:items].any? { |item| match_action(item[:action]) }
36
+ end
37
+
38
+ def match_action(action)
39
+ return true if @expected.nil?
40
+ return true if ::RSpec::Matchers::BuiltIn::Include.new(@expected).matches?(action)
41
+
42
+ false
43
+ end
44
+ end
45
+
46
+ def have_line_quick_reply(expected = nil) # rubocop:disable Naming/PredicateName
47
+ HaveQuickReply.new(expected)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,17 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec::Matchers.define :have_line_text_message do |expected|
4
- match do |actual|
5
- actual.each do |message|
6
- next unless message[:type] == "text"
3
+ module Line
4
+ module Message
5
+ module RSpec
6
+ # :nodoc:
7
+ module Matchers
8
+ # The text message matcher for RSpec to search for text messages in the message array.
9
+ class HaveTextMessage
10
+ def initialize(expected)
11
+ @expected = expected
12
+ end
7
13
 
8
- return true if message[:text].match?(expected)
9
- end
14
+ def description
15
+ return "have text message" if @expected.nil?
10
16
 
11
- false
12
- end
17
+ "have text message matching #{@expected.inspect}"
18
+ end
19
+
20
+ def matches?(actual)
21
+ @actual = actual
22
+ @actual.each do |message|
23
+ next unless message[:type] == "text"
24
+
25
+ return true if message[:text].match?(@expected)
26
+ end
13
27
 
14
- failure_message do |_actual|
15
- "expected to find a text message matching #{expected}"
28
+ false
29
+ end
30
+
31
+ def failure_message
32
+ "expected to find a text message matching #{@expected}"
33
+ end
34
+ end
35
+
36
+ def have_line_text_message(expected = nil) # rubocop:disable Naming/PredicateName
37
+ HaveTextMessage.new(expected)
38
+ end
39
+ end
40
+ end
16
41
  end
17
42
  end
@@ -1,3 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "matchers/have_text_message"
4
+ require_relative "matchers/have_quick_reply"
5
+
6
+ require_relative "matchers/have_flex_message"
7
+ require_relative "matchers/have_flex_component"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: line-message-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-04 00:00:00.000000000 Z
11
+ date: 2025-04-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The LINE Messaging API message builder.
14
14
  email:
@@ -17,8 +17,10 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".release-please-manifest.json"
20
21
  - ".rspec"
21
22
  - ".rubocop.yml"
23
+ - CHANGELOG.md
22
24
  - CONVENTIONS.md
23
25
  - LICENSE.txt
24
26
  - README.md
@@ -28,12 +30,22 @@ files:
28
30
  - lib/line/message/builder/actions/message.rb
29
31
  - lib/line/message/builder/actions/postback.rb
30
32
  - lib/line/message/builder/base.rb
33
+ - lib/line/message/builder/container.rb
34
+ - lib/line/message/builder/flex.rb
35
+ - lib/line/message/builder/flex/box.rb
36
+ - lib/line/message/builder/flex/bubble.rb
37
+ - lib/line/message/builder/flex/builder.rb
38
+ - lib/line/message/builder/flex/button.rb
39
+ - lib/line/message/builder/flex/image.rb
40
+ - lib/line/message/builder/flex/text.rb
31
41
  - lib/line/message/builder/quick_reply.rb
32
- - lib/line/message/builder/quick_reply/builder.rb
33
42
  - lib/line/message/builder/text.rb
34
43
  - lib/line/message/builder/version.rb
35
44
  - lib/line/message/rspec.rb
36
45
  - lib/line/message/rspec/matchers.rb
46
+ - lib/line/message/rspec/matchers/have_flex_component.rb
47
+ - lib/line/message/rspec/matchers/have_flex_message.rb
48
+ - lib/line/message/rspec/matchers/have_quick_reply.rb
37
49
  - lib/line/message/rspec/matchers/have_text_message.rb
38
50
  - release-please-config.json
39
51
  - sig/line/message/builder.rbs
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Line
4
- module Message
5
- class Builder
6
- module QuickReply
7
- # The Builder class is used to build quick reply buttons.
8
- class Builder < Line::Message::Builder::Base
9
- def initialize(context: nil, &)
10
- @items = []
11
-
12
- super
13
- end
14
-
15
- def message(text, label:, image_url: nil, &)
16
- action(
17
- Actions::Message.new(text: text, label: label, &).to_h,
18
- image_url
19
- )
20
- end
21
-
22
- def postback(data, label: nil, display_text: nil, image_url: nil, &)
23
- action(
24
- Actions::Postback.new(data: data, label: label, display_text: display_text, &).to_h,
25
- image_url
26
- )
27
- end
28
-
29
- def to_h
30
- { items: @items.map(&:to_h) }
31
- end
32
-
33
- private
34
-
35
- def action(action, image_url)
36
- @items << {
37
- type: "action",
38
- imageUrl: image_url,
39
- action: action
40
- }.compact
41
- end
42
- end
43
- end
44
- end
45
- end
46
- end