line-message-builder 0.3.0 → 0.5.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: aa7a2c0c112ef20086d81a2c1cf4b1c430ffe291c07ccfc92dfe8d81db1ea980
4
- data.tar.gz: 163eb284640eeec1e5a035492ebca95b34d703d372f5d472e4330a762d7b2ceb
3
+ metadata.gz: 8051ec09aa3f71ec93dc5cbe685610fd6fe6d5c4a3b086f5b5ddbef6b98e7c3c
4
+ data.tar.gz: cd043754cc232ed8b4e8cec6751413bf2f87e9cd3ea4b23f521346cb1ca7a86d
5
5
  SHA512:
6
- metadata.gz: bcadb195ad72f851fcc648f6641ab547fc2a1885583f64be97f08288f43025246d2a14dd311103a5c64521e636dfb2bd0f89f61deccedec75ebe8bebdc528d2c
7
- data.tar.gz: 1d612904e46b6e3e87590edf414b7839fa18412fe1c4734cdb961aacc8c1ed2e87a87a425b3cf563cc1eff4ec7337deaf0ff81592b75520dabba78363ec67629
6
+ metadata.gz: 4d951b1938f6a27ad366e5b4f08e1de7496c5d130ac95e40d4f15c2ca86a928a19a5cfb46184d958dae6afd664dfa68d4c50a2d7e00c172445e42205e3987abc
7
+ data.tar.gz: 802c37d5e9d2beb7f641046bf8223e9a4fc26290b5e9f1a5a07b1754027509b1b2b63952be93622908674d14321fe9c4cceea770aaf2d56684caaa94b1bb7d69
@@ -1 +1 @@
1
- {".":"0.3.0"}
1
+ {".":"0.5.0"}
data/.rspec CHANGED
@@ -1,3 +1,5 @@
1
1
  --format documentation
2
+ --format RspecJunitFormatter
3
+ --out junit.xml
2
4
  --color
3
5
  --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/elct9620/line-message-builder/compare/v0.4.0...v0.5.0) (2025-04-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add Actionable module for flex components with message and postback actions ([9ac4b4a](https://github.com/elct9620/line-message-builder/commit/9ac4b4af591450e8f1925814b5a57e893ffa15db))
9
+ * Add enum validator for flex text alignment options ([540c58d](https://github.com/elct9620/line-message-builder/commit/540c58d94731adfe91d30e006bb0ee6714755006))
10
+ * Add justify_content and align_items options to Flex Box builder ([6c2015f](https://github.com/elct9620/line-message-builder/commit/6c2015f0a75a0f541cb52fef647e0bff1a245a28))
11
+ * Add layout validation for flex box with enum validator ([b1ecba1](https://github.com/elct9620/line-message-builder/commit/b1ecba160db03f94aaebc83b82158e75292c94eb))
12
+ * Add margin support for flex components in Line message builder ([36043b8](https://github.com/elct9620/line-message-builder/commit/36043b8d7390cb13d60174759fc5906a70aa2507))
13
+ * Add offset positioning support for flex message components ([1e28336](https://github.com/elct9620/line-message-builder/commit/1e2833656083aa72b0cfffccd5636e80ae41ccc9))
14
+ * Add padding support for flex box components with validation ([9dcd9d2](https://github.com/elct9620/line-message-builder/commit/9dcd9d20a48aed023440a2abe24e8b44eea8b425))
15
+ * Add padding support for Line Flex buttons with validation ([ca25a11](https://github.com/elct9620/line-message-builder/commit/ca25a1192c0d611ae064f935fc9335640d123b2d))
16
+ * Add SimpleCov Cobertura formatter for CI coverage reporting ([bb5bfc8](https://github.com/elct9620/line-message-builder/commit/bb5bfc8344c1b448e61a2857aa36e1f6b5ce0425))
17
+ * Add validator for spacing option in flex box builder ([06e7b98](https://github.com/elct9620/line-message-builder/commit/06e7b9805f8243c51faaf9c13c264ffb1f35091c))
18
+ * Add vertical positioning support for Flex message components ([f9ce97b](https://github.com/elct9620/line-message-builder/commit/f9ce97b666cabe47717cdd916722121372cb9bf0))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * Correct indentation in GitHub Actions workflow file ([1c1cd2d](https://github.com/elct9620/line-message-builder/commit/1c1cd2dfe2e48bd937a04ac25f7b2b772e02e1a6))
24
+
25
+ ## [0.4.0](https://github.com/elct9620/line-message-builder/compare/v0.3.0...v0.4.0) (2025-04-06)
26
+
27
+
28
+ ### Features
29
+
30
+ * Add flex and margin options to Line Flex Button with improved error handling ([c8cd142](https://github.com/elct9620/line-message-builder/commit/c8cd1427528d5495d13bf93898da4638588065ae))
31
+ * Add flex box options and validation for LINE message builder ([133f87d](https://github.com/elct9620/line-message-builder/commit/133f87ddb3fd2a093947db0aef6363cd4c150701))
32
+ * Add flex bubble options and RSpec matcher for Line message builder ([605ad89](https://github.com/elct9620/line-message-builder/commit/605ad89870a53c55182f96db5f2738b5c347f0dc))
33
+ * Add margin option to flex text builder ([1d44522](https://github.com/elct9620/line-message-builder/commit/1d44522a44218bedab3d2009ef483ed112784248))
34
+ * Add quote token support for Line text messages ([7871999](https://github.com/elct9620/line-message-builder/commit/7871999dfc7032e75e5407c8b16bed7ef7dd28b8))
35
+ * Add support for flex, margin, and align options in Flex image builder ([693d0ab](https://github.com/elct9620/line-message-builder/commit/693d0abf5ef524b2e6d075f3c7e65eca49cf7f27))
36
+
3
37
  ## [0.3.0](https://github.com/elct9620/line-message-builder/compare/v0.2.0...v0.3.0) (2025-04-05)
4
38
 
5
39
 
data/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # LINE Message Builder
2
2
 
3
+ [![.github/workflows/main.yml](https://github.com/elct9620/line-message-builder/actions/workflows/main.yml/badge.svg)](https://github.com/elct9620/line-message-builder/actions/workflows/main.yml)
4
+ [![codecov](https://codecov.io/gh/elct9620/line-message-builder/graph/badge.svg?token=9TJTSRIL0X)](https://codecov.io/gh/elct9620/line-message-builder)
5
+
3
6
  Build LINE messages using DSL (Domain Specific Language) in Ruby.
4
7
 
8
+ ## Features
9
+
10
+ - Build LINE messages using DSL
11
+ - Validation of properties
12
+ - RSpec matchers for testing
13
+
5
14
  ## Installation
6
15
 
7
16
  Install the gem and add to the application's Gemfile by executing:
@@ -95,6 +104,7 @@ end
95
104
  | --------------------------- | ------------------------------------ |
96
105
  | `have_line_text_message` | Match a text message |
97
106
  | `have_line_flex_message` | Match a flex message |
107
+ | `have_line_flex_bubble` | Match a flex message with bubble |
98
108
  | `have_line_flex_text` | Match a flex message with text |
99
109
  | `have_line_flex_image` | Match a flex message with image |
100
110
  | `have_line_flex_button` | Match a flex message with button |
@@ -154,7 +164,7 @@ end
154
164
 
155
165
  | Type | Supported |
156
166
  | ---- | --------- |
157
- | Text | |
167
+ | Text | 🚧 |
158
168
  | Text v2 | ❌ |
159
169
  | Sticker | ❌ |
160
170
  | Sticker | ❌ |
@@ -189,19 +199,19 @@ end
189
199
 
190
200
  ### Flex Components
191
201
 
192
- | Component | Supported |
193
- | --------- | --------- |
194
- | Bubble | 🚧 |
195
- | Carousel | ❌ |
196
- | Box | 🚧 |
197
- | Button | 🚧 |
198
- | Image | 🚧 |
199
- | Video | ❌ |
200
- | Icon | ❌ |
201
- | Text | 🚧 |
202
- | Span | ❌ |
203
- | Separator | ❌ |
204
- | Filler | ❌ |
202
+ | Component | Supported |
203
+ | --------- | --------- |
204
+ | Bubble | 🚧 |
205
+ | Carousel | ❌ |
206
+ | Box | 🚧 |
207
+ | Button | 🚧 |
208
+ | Image | 🚧 |
209
+ | Video | ❌ |
210
+ | Icon | ❌ |
211
+ | Text | 🚧 |
212
+ | Span | ❌ |
213
+ | Separator | ❌ |
214
+ | Filler | ❌ Deprecated |
205
215
 
206
216
  ## Development
207
217
 
@@ -6,26 +6,23 @@ module Line
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
9
- def initialize(text: nil, label: nil, context: nil, &)
10
- @text = text
11
- @label = label
12
-
13
- super(context: context, &)
14
- end
9
+ attr_reader :text
15
10
 
16
- def label(label)
17
- @label = label
18
- end
11
+ option :label, default: nil
19
12
 
20
- def text(text)
13
+ def initialize(text, context: nil, **options, &)
21
14
  @text = text
15
+
16
+ super(context: context, **options, &)
22
17
  end
23
18
 
24
19
  def to_h
20
+ raise RequiredError, "text is required" if text.nil?
21
+
25
22
  {
26
23
  type: "message",
27
- label: @label,
28
- text: @text
24
+ label: label,
25
+ text: text
29
26
  }.compact
30
27
  end
31
28
  end
@@ -6,32 +6,25 @@ module Line
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
9
- def initialize(data: nil, label: nil, display_text: nil, context: nil, &)
10
- @data = data
11
- @label = label
12
- @display_text = display_text
13
-
14
- super(context: context, &)
15
- end
9
+ attr_reader :data
16
10
 
17
- def label(label)
18
- @label = label
19
- end
11
+ option :label, default: nil
12
+ option :display_text, default: nil
20
13
 
21
- def data(data)
14
+ def initialize(data, context: nil, **options, &)
22
15
  @data = data
23
- end
24
16
 
25
- def display_text(display_text)
26
- @display_text = display_text
17
+ super(context: context, **options, &)
27
18
  end
28
19
 
29
20
  def to_h
21
+ raise RequiredError, "data is required" if data.nil?
22
+
30
23
  {
31
24
  type: "postback",
32
- label: @label,
33
- data: @data,
34
- displayText: @display_text
25
+ label: label,
26
+ data: data,
27
+ displayText: display_text
35
28
  }.compact
36
29
  end
37
30
  end
@@ -18,13 +18,14 @@ module Line
18
18
  @options ||= []
19
19
  end
20
20
 
21
- def option(name, default: nil)
21
+ def option(name, default: nil, validator: nil)
22
22
  options << name
23
23
 
24
24
  define_method name do |*args|
25
25
  if args.empty?
26
26
  instance_variable_get("@#{name}") || default
27
27
  else
28
+ validator&.valid!(args.first)
28
29
  instance_variable_set("@#{name}", args.first)
29
30
  end
30
31
  end
@@ -38,7 +39,7 @@ module Line
38
39
  @quick_reply = nil
39
40
 
40
41
  self.class.options.each do |option|
41
- instance_variable_set("@#{option}", options[option]) if options.key?(option)
42
+ send(option, options[option]) if options.key?(option)
42
43
  end
43
44
 
44
45
  instance_eval(&block) if ::Kernel.block_given?
@@ -13,12 +13,12 @@ module Line
13
13
  super
14
14
  end
15
15
 
16
- def text(text, &)
17
- @messages << Text.new(text, context: context, &)
16
+ def text(text, **options, &)
17
+ @messages << Text.new(text, context: context, **options, &)
18
18
  end
19
19
 
20
- def flex(**args, &)
21
- @messages << Flex::Builder.new(**args, context: context, &)
20
+ def flex(**options, &)
21
+ @messages << Flex::Builder.new(context: context, **options, &)
22
22
  end
23
23
 
24
24
  def build
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # The action DSL for flex components.
8
+ module Actionable
9
+ def self.included(base)
10
+ base.attr_reader :action
11
+ end
12
+
13
+ def message(text, **options, &)
14
+ @action = Actions::Message.new(text, context: context, **options, &)
15
+ end
16
+
17
+ def postback(data, **options, &)
18
+ @action = Actions::Postback.new(data, context: context, **options, &)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -6,7 +6,24 @@ module Line
6
6
  module Flex
7
7
  # The box is a component for the Flex message.
8
8
  class Box < Line::Message::Builder::Base
9
- option :layout, default: :horizontal
9
+ include Actionable
10
+ include Position::Padding
11
+ include Position::Margin
12
+ include Position::Offset
13
+
14
+ attr_reader :contents
15
+
16
+ option :layout, default: :horizontal, validator: Validators::Enum.new(
17
+ :horizontal, :vertical, :baseline
18
+ )
19
+ option :justify_content, default: nil, validator: Validators::Enum.new(
20
+ :flex_start, :center, :flex_end, :space_between, :space_around, :space_evenly
21
+ )
22
+ option :align_items, default: nil, validator: Validators::Enum.new(
23
+ :flex_start, :center, :flex_end
24
+ )
25
+ option :spacing, default: nil, validator: Validators::Size.new
26
+ option :flex, default: nil
10
27
 
11
28
  def initialize(context: nil, **options, &)
12
29
  @contents = []
@@ -30,11 +47,33 @@ module Line
30
47
  @contents << Flex::Image.new(url, context: context, **options, &)
31
48
  end
32
49
 
33
- def to_h
50
+ def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
51
+ raise RequiredError, "layout is required" if layout.nil?
52
+
34
53
  {
35
54
  type: "box",
36
- layout: @layout,
37
- contents: @contents.map(&:to_h)
55
+ layout: layout,
56
+ # Position
57
+ justifyContent: justify_content,
58
+ alignItems: align_items,
59
+ spacing: spacing,
60
+ # Position::Padding
61
+ paddingAll: padding,
62
+ paddingTop: padding_top,
63
+ paddingBottom: padding_bottom,
64
+ paddingStart: padding_start,
65
+ paddingEnd: padding_end,
66
+ # Position::Margin
67
+ margin: margin,
68
+ # Position::Offset
69
+ position: position,
70
+ offsetTop: offset_top,
71
+ offsetBottom: offset_bottom,
72
+ offsetStart: offset_start,
73
+ offsetEnd: offset_end,
74
+ flex: flex,
75
+ contents: contents.map(&:to_h),
76
+ action: action&.to_h
38
77
  }.compact
39
78
  end
40
79
  end
@@ -6,7 +6,10 @@ module Line
6
6
  module Flex
7
7
  # The bubble is container for the Flex message.
8
8
  class Bubble < Line::Message::Builder::Base
9
- def initialize(context: nil, &)
9
+ option :size, default: nil
10
+ option :styles, default: nil
11
+
12
+ def initialize(context: nil, **options, &)
10
13
  @header = nil
11
14
  @hero = nil
12
15
  @body = nil
@@ -41,7 +44,9 @@ module Line
41
44
  header: @header&.to_h,
42
45
  hero: @hero&.to_h,
43
46
  body: @body&.to_h,
44
- footer: @footer&.to_h
47
+ footer: @footer&.to_h,
48
+ size: size,
49
+ styles: styles
45
50
  }.compact
46
51
  end
47
52
  end
@@ -14,8 +14,8 @@ module Line
14
14
  super
15
15
  end
16
16
 
17
- def bubble(&)
18
- @contents = Line::Message::Builder::Flex::Bubble.new(context: context, &)
17
+ def bubble(**options, &)
18
+ @contents = Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
19
19
  end
20
20
 
21
21
  def to_h
@@ -6,31 +6,41 @@ module Line
6
6
  module Flex
7
7
  # The button is a component of the Flex message.
8
8
  class Button < Line::Message::Builder::Base
9
+ include Actionable
10
+ include Position::Vertical
11
+ include Position::Padding
12
+ include Position::Margin
13
+ include Position::Offset
14
+
15
+ option :flex, default: nil
9
16
  option :style, default: :link
10
17
  option :height, default: :md
11
18
 
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
19
+ def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
20
+ raise RequiredError, "action is required" if action.nil?
28
21
 
29
22
  {
30
23
  type: "button",
31
- action: @action.to_h,
32
- style: @style,
33
- height: @height
24
+ action: action.to_h,
25
+ # Position
26
+ grivity: gravity,
27
+ # Position::Padding
28
+ paddingAll: padding,
29
+ paddingTop: padding_top,
30
+ paddingBottom: padding_bottom,
31
+ paddingStart: padding_start,
32
+ paddingEnd: padding_end,
33
+ # Position::Margin
34
+ margin: margin,
35
+ # Position::Offset
36
+ position: position,
37
+ offsetTop: offset_top,
38
+ offsetBottom: offset_bottom,
39
+ offsetStart: offset_start,
40
+ offsetEnd: offset_end,
41
+ flex: flex,
42
+ style: style,
43
+ height: height
34
44
  }.compact
35
45
  end
36
46
  end
@@ -6,9 +6,18 @@ module Line
6
6
  module Flex
7
7
  # The image is a component for the Flex message.
8
8
  class Image < Line::Message::Builder::Base
9
+ include Actionable
10
+ include Position::Horizontal
11
+ include Position::Vertical
12
+ include Position::Margin
13
+ include Position::Offset
14
+
15
+ attr_reader :url
16
+
9
17
  option :size, default: nil
10
18
  option :aspect_ratio, default: nil
11
19
  option :aspect_mode, default: nil
20
+ option :flex, default: nil
12
21
 
13
22
  def initialize(url, context: nil, **options, &)
14
23
  @url = url
@@ -16,25 +25,28 @@ module Line
16
25
  super(context: context, **options, &)
17
26
  end
18
27
 
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
28
+ def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
29
+ raise RequiredError, "url is required" if url.nil?
30
30
 
31
- def to_h
32
31
  {
33
32
  type: "image",
34
- url: @url,
35
- size: @size,
36
- aspectRatio: @aspect_ratio,
37
- aspectMode: @aspect_mode
33
+ url: url,
34
+ # Position
35
+ align: align,
36
+ gravity: gravity,
37
+ # Position::Margin
38
+ margin: margin,
39
+ # Position::Offset
40
+ position: position,
41
+ offsetTop: offset_top,
42
+ offsetBottom: offset_bottom,
43
+ offsetStart: offset_start,
44
+ offsetEnd: offset_end,
45
+ size: size,
46
+ flex: flex,
47
+ aspectRatio: aspect_ratio,
48
+ aspectMode: aspect_mode,
49
+ action: action&.to_h
38
50
  }.compact
39
51
  end
40
52
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ module Position
8
+ # The horizontal provides "align" options for flex components.
9
+ module Horizontal
10
+ def self.included(base)
11
+ base.option :align,
12
+ default: :nil,
13
+ validator: Validators::Enum.new(
14
+ :start, :center, :end
15
+ )
16
+ end
17
+ end
18
+
19
+ # The vertical provides "gatvity" options for flex components.
20
+ module Vertical
21
+ def self.included(base)
22
+ base.option :gravity,
23
+ default: :nil,
24
+ validator: Validators::Enum.new(
25
+ :top, :center, :bottom
26
+ )
27
+ end
28
+ end
29
+
30
+ # The padding provides "padding" options for flex components.
31
+ module Padding
32
+ def self.included(base)
33
+ %i[padding padding_top padding_bottom padding_start padding_end].each do |option|
34
+ base.option option,
35
+ default: :nil,
36
+ validator: Validators::PercentageSize.new
37
+ end
38
+ end
39
+ end
40
+
41
+ # The margin provides "margin" options for flex components.
42
+ module Margin
43
+ def self.included(base)
44
+ base.option :margin,
45
+ default: :nil,
46
+ validator: Validators::Size.new
47
+ end
48
+ end
49
+
50
+ # The offset provides "offset" options for flex components.
51
+ module Offset
52
+ def self.included(base)
53
+ base.option :position,
54
+ default: :nil,
55
+ validator: Validators::Enum.new(:absolute, :relative)
56
+
57
+ %i[offset_top offset_bottom offset_start offset_end].each do |option|
58
+ base.option option,
59
+ default: :nil,
60
+ validator: Validators::Size.new
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -6,11 +6,18 @@ module Line
6
6
  module Flex
7
7
  # The text is a component of the Flex message.
8
8
  class Text < Line::Message::Builder::Base
9
+ include Actionable
10
+ include Position::Horizontal
11
+ include Position::Vertical
12
+ include Position::Margin
13
+ include Position::Offset
14
+
15
+ attr_reader :text
16
+
17
+ option :size, default: nil
9
18
  option :wrap, default: false
10
19
  option :line_spacing, default: nil
11
20
  option :color, default: nil
12
- option :size, default: nil
13
- option :align, default: nil
14
21
  option :flex, default: nil
15
22
 
16
23
  def initialize(text, context: nil, **options, &)
@@ -23,16 +30,29 @@ module Line
23
30
  @wrap = true
24
31
  end
25
32
 
26
- def to_h
33
+ def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
34
+ raise RequiredError, "text is required" if text.nil?
35
+
27
36
  {
28
37
  type: "text",
29
- text: @text,
30
- wrap: @wrap,
31
- lineSpacing: @line_spacing,
32
- color: @color,
33
- size: @size,
34
- align: @align,
35
- flex: @flex
38
+ text: text,
39
+ # Position
40
+ align: align,
41
+ gravity: gravity,
42
+ # Position::Margin
43
+ margin: margin,
44
+ # Position::Offset
45
+ position: position,
46
+ offsetTop: offset_top,
47
+ offsetBottom: offset_bottom,
48
+ offsetStart: offset_start,
49
+ offsetEnd: offset_end,
50
+ wrap: wrap,
51
+ lineSpacing: line_spacing,
52
+ color: color,
53
+ size: size,
54
+ flex: flex,
55
+ action: action&.to_h
36
56
  }.compact
37
57
  end
38
58
  end
@@ -6,6 +6,8 @@ module Line
6
6
  # The Flex module allows to build Flex messages.
7
7
  module Flex
8
8
  require_relative "flex/builder"
9
+ require_relative "flex/actionable"
10
+ require_relative "flex/position"
9
11
 
10
12
  # Container
11
13
  require_relative "flex/bubble"
@@ -13,14 +13,14 @@ module Line
13
13
 
14
14
  def message(text, label:, image_url: nil, &)
15
15
  action(
16
- Actions::Message.new(text: text, label: label, &),
16
+ Actions::Message.new(text, label: label, &),
17
17
  image_url
18
18
  )
19
19
  end
20
20
 
21
21
  def postback(data, label: nil, display_text: nil, image_url: nil, &)
22
22
  action(
23
- Actions::Postback.new(data: data, label: label, display_text: display_text, &),
23
+ Actions::Postback.new(data, label: label, display_text: display_text, &),
24
24
  image_url
25
25
  )
26
26
  end
@@ -5,16 +5,19 @@ module Line
5
5
  module Builder
6
6
  # Text message builder.
7
7
  class Text < Base
8
- def initialize(text, context: nil, &block)
8
+ option :quote_token, default: nil
9
+
10
+ def initialize(text, context: nil, **options, &block)
9
11
  @text = text
10
12
 
11
- super(context: context, &block)
13
+ super(context: context, **options, &block)
12
14
  end
13
15
 
14
16
  def to_h
15
17
  {
16
18
  type: "text",
17
19
  text: @text,
20
+ quoteToken: quote_token,
18
21
  quickReply: @quick_reply&.to_h
19
22
  }.compact
20
23
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Validators
7
+ # Validate values against a set of allowed values.
8
+ class Enum
9
+ attr_reader :allowed_values
10
+
11
+ def initialize(*allowed_values)
12
+ @allowed_values = allowed_values
13
+ end
14
+
15
+ def valid!(value)
16
+ return if allowed_values.include?(value.to_sym)
17
+
18
+ raise ValidationError, "Invalid value: #{value}. Allowed values are: #{allowed_values.join(", ")}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Validators
7
+ # Validate size values for LINE messages.
8
+ class Size
9
+ KEYWORDS = %i[none xs sm md lg xl xxl].freeze
10
+
11
+ def valid?(value)
12
+ is_keyword = KEYWORDS.include?(value.to_sym)
13
+ is_pixels = value.end_with?("px")
14
+ is_keyword || is_pixels
15
+ end
16
+
17
+ def valid!(value)
18
+ return if valid?(value)
19
+
20
+ raise ValidationError,
21
+ "Invalid value: #{value}. Allowed values are: #{KEYWORDS.join(", ")} or a pixel value (e.g., '100px')"
22
+ end
23
+ end
24
+
25
+ # Validate size values with percentage.
26
+ class PercentageSize < Size
27
+ def valid!(value)
28
+ is_percentage = value.end_with?("%")
29
+ return if is_percentage || valid?(value)
30
+
31
+ raise ValidationError,
32
+ "Invalid value: #{value}. Allowed values are: #{KEYWORDS.join(", ")}, " \
33
+ "a pixel value (e.g., '100px'), or a percentage (e.g., '50%')"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ # :nodoc:
7
+ module Validators
8
+ require_relative "validators/enum"
9
+ require_relative "validators/size"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,7 +3,7 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- VERSION = "0.3.0"
6
+ VERSION = "0.5.0"
7
7
  end
8
8
  end
9
9
  end
@@ -7,8 +7,11 @@ module Line
7
7
  # The Builder module provides a DSL for building LINE messages.
8
8
  module Builder
9
9
  class Error < StandardError; end
10
+ class RequiredError < Error; end
11
+ class ValidationError < Error; end
10
12
 
11
13
  require_relative "builder/base"
14
+ require_relative "builder/validators"
12
15
  require_relative "builder/actions"
13
16
  require_relative "builder/quick_reply"
14
17
  require_relative "builder/container"
@@ -0,0 +1,60 @@
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 HaveFlexBubble
10
+ def initialize(expected)
11
+ @expected = Utils.stringify_keys!(expected || {}, deep: true)
12
+ end
13
+
14
+ def description
15
+ return "have flex bubble" if @expected.empty?
16
+
17
+ "have flex bubble matching #{@expected.inspect}"
18
+ end
19
+
20
+ def matches?(actual)
21
+ @actual = Utils.stringify_keys!(actual, deep: true)
22
+ @actual.any? { |message| match_flex_component?(message) }
23
+ end
24
+ alias == matches?
25
+
26
+ def failure_message
27
+ return "expected to find a flex bubble" if @expected.empty?
28
+
29
+ "expected to find a flex bubble matching #{@expected.inspect}"
30
+ end
31
+
32
+ private
33
+
34
+ def match_flex_component?(message)
35
+ return false unless message["type"] == "flex"
36
+
37
+ match_content?(message["contents"])
38
+ end
39
+
40
+ def match_content?(content)
41
+ return match_options?(content) if content["type"] == "bubble"
42
+ return false unless content["contents"]
43
+
44
+ content["contents"].any? { |nested_content| match_content?(nested_content) }
45
+ end
46
+
47
+ def match_options?(content)
48
+ return false unless content["type"] == "bubble"
49
+
50
+ ::RSpec::Matchers::BuiltIn::Include.new(@expected).matches?(content)
51
+ end
52
+ end
53
+
54
+ def have_line_flex_bubble(expected = nil) # rubocop:disable Naming/PredicateName
55
+ HaveFlexBubble.new(expected)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -8,33 +8,37 @@ module Line
8
8
  # The text message matcher for RSpec to search for text messages in the message array.
9
9
  class HaveTextMessage
10
10
  def initialize(expected)
11
- @expected = expected
11
+ @text, @options = expected
12
+ @options = Utils.stringify_keys!(@options || {})
12
13
  end
13
14
 
14
15
  def description
15
- return "have text message" if @expected.nil?
16
+ return "have text message" if @text.nil?
17
+ return "have text message matching #{@text.inspect}" if @options.empty?
16
18
 
17
- "have text message matching #{@expected.inspect}"
19
+ "have text message matching #{@text.inspect} with options #{@options.inspect}"
18
20
  end
19
21
 
20
22
  def matches?(actual)
21
23
  @actual = Utils.stringify_keys!(actual, deep: true)
22
- @actual.each do |message|
24
+ @actual.any? do |message|
23
25
  next unless message["type"] == "text"
26
+ next true if @text.nil?
24
27
 
25
- return true if message["text"].match?(@expected)
28
+ message["text"].match?(@text) && ::RSpec::Matchers::BuiltIn::Include.new(@options).matches?(message)
26
29
  end
27
-
28
- false
29
30
  end
30
31
  alias == matches?
31
32
 
32
33
  def failure_message
33
- "expected to find a text message matching #{@expected}"
34
+ return "expected to find a text message" if @text.nil?
35
+ return "expected to find a text message matching #{@text.inspect}" if @options.empty?
36
+
37
+ "expected to find a text message matching #{@text.inspect} with options #{@options.inspect}"
34
38
  end
35
39
  end
36
40
 
37
- def have_line_text_message(expected = nil) # rubocop:disable Naming/PredicateName
41
+ def have_line_text_message(*expected) # rubocop:disable Naming/PredicateName
38
42
  HaveTextMessage.new(expected)
39
43
  end
40
44
  end
@@ -4,4 +4,5 @@ require_relative "matchers/utils"
4
4
  require_relative "matchers/have_text_message"
5
5
  require_relative "matchers/have_quick_reply"
6
6
  require_relative "matchers/have_flex_message"
7
+ require_relative "matchers/have_flex_bubble"
7
8
  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.3.0
4
+ version: 0.5.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-05 00:00:00.000000000 Z
11
+ date: 2025-04-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The LINE Messaging API message builder.
14
14
  email:
@@ -32,17 +32,23 @@ files:
32
32
  - lib/line/message/builder/base.rb
33
33
  - lib/line/message/builder/container.rb
34
34
  - lib/line/message/builder/flex.rb
35
+ - lib/line/message/builder/flex/actionable.rb
35
36
  - lib/line/message/builder/flex/box.rb
36
37
  - lib/line/message/builder/flex/bubble.rb
37
38
  - lib/line/message/builder/flex/builder.rb
38
39
  - lib/line/message/builder/flex/button.rb
39
40
  - lib/line/message/builder/flex/image.rb
41
+ - lib/line/message/builder/flex/position.rb
40
42
  - lib/line/message/builder/flex/text.rb
41
43
  - lib/line/message/builder/quick_reply.rb
42
44
  - lib/line/message/builder/text.rb
45
+ - lib/line/message/builder/validators.rb
46
+ - lib/line/message/builder/validators/enum.rb
47
+ - lib/line/message/builder/validators/size.rb
43
48
  - lib/line/message/builder/version.rb
44
49
  - lib/line/message/rspec.rb
45
50
  - lib/line/message/rspec/matchers.rb
51
+ - lib/line/message/rspec/matchers/have_flex_bubble.rb
46
52
  - lib/line/message/rspec/matchers/have_flex_component.rb
47
53
  - lib/line/message/rspec/matchers/have_flex_message.rb
48
54
  - lib/line/message/rspec/matchers/have_quick_reply.rb