Repository: line/line-bot-sdk-go
Pull request #279 (2021-04-23 05:35:38)

Add test case for #272
helped #272 
add test case.

@kkdai 
FYI: https://github.com/line/line-bot-sdk-go/pull/272#pullrequestreview-631020398


Patch:
diff --git a/linebot/flex.go b/linebot/flex.go
index 199895c..90105cf 100644
--- a/linebot/flex.go
+++ b/linebot/flex.go
@@ -152,6 +152,15 @@ const (
 	FlexBoxLayoutTypeBaseline   FlexBoxLayoutType = "baseline"
 )
 
+// FlexComponentPositionType type
+type FlexComponentPositionType string
+
+// FlexComponentPositionType constants
+const (
+	FlexComponentPositionTypeRelative FlexComponentPositionType = "relative"
+	FlexComponentPositionTypeAbsolute FlexComponentPositionType = "absolute"
+)
+
 // FlexComponentSpacingType type
 type FlexComponentSpacingType string
 
@@ -180,6 +189,34 @@ const (
 	FlexComponentMarginTypeXxl  FlexComponentMarginType = "xxl"
 )
 
+// FlexComponentOffsetType type
+type FlexComponentOffsetType string
+
+// FlexComponentOffsetType constants
+const (
+	FlexComponentOffsetTypeNone FlexComponentOffsetType = "none"
+	FlexComponentOffsetTypeXs   FlexComponentOffsetType = "xs"
+	FlexComponentOffsetTypeSm   FlexComponentOffsetType = "sm"
+	FlexComponentOffsetTypeMd   FlexComponentOffsetType = "md"
+	FlexComponentOffsetTypeLg   FlexComponentOffsetType = "lg"
+	FlexComponentOffsetTypeXl   FlexComponentOffsetType = "xl"
+	FlexComponentOffsetTypeXxl  FlexComponentOffsetType = "xxl"
+)
+
+// FlexComponentPaddingType type
+type FlexComponentPaddingType string
+
+// FlexComponentPaddingType constants
+const (
+	FlexComponentPaddingTypeNone FlexComponentPaddingType = "none"
+	FlexComponentPaddingTypeXs   FlexComponentPaddingType = "xs"
+	FlexComponentPaddingTypeSm   FlexComponentPaddingType = "sm"
+	FlexComponentPaddingTypeMd   FlexComponentPaddingType = "md"
+	FlexComponentPaddingTypeLg   FlexComponentPaddingType = "lg"
+	FlexComponentPaddingTypeXl   FlexComponentPaddingType = "xl"
+	FlexComponentPaddingTypeXxl  FlexComponentPaddingType = "xxl"
+)
+
 // FlexComponentGravityType type
 type FlexComponentGravityType string
 
@@ -441,6 +478,7 @@ type FlexComponent interface {
 type BoxComponent struct {
 	Type            FlexComponentType
 	Layout          FlexBoxLayoutType
+	Position        FlexComponentPositionType
 	Contents        []FlexComponent
 	Flex            *int
 	Spacing         FlexComponentSpacingType
@@ -454,6 +492,15 @@ type BoxComponent struct {
 	JustifyContent  FlexComponentJustifyContentType
 	AlignItems      FlexComponentAlignItemsType
 	Background      *BoxBackground
+	OffsetTop       FlexComponentOffsetType
+	OffsetBottom    FlexComponentOffsetType
+	OffsetStart     FlexComponentOffsetType
+	OffsetEnd       FlexComponentOffsetType
+	PaddingAll      FlexComponentPaddingType
+	PaddingTop      FlexComponentPaddingType
+	PaddingBottom   FlexComponentPaddingType
+	PaddingStart    FlexComponentPaddingType
+	PaddingEnd      FlexComponentPaddingType
 }
 
 // MarshalJSON method of BoxComponent
@@ -461,6 +508,7 @@ func (c *BoxComponent) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&struct {
 		Type            FlexComponentType               `json:"type"`
 		Layout          FlexBoxLayoutType               `json:"layout"`
+		Position        FlexComponentPositionType       `json:"position,omitempty"`
 		Contents        []FlexComponent                 `json:"contents"`
 		Flex            *int                            `json:"flex,omitempty"`
 		Spacing         FlexComponentSpacingType        `json:"spacing,omitempty"`
@@ -474,9 +522,19 @@ func (c *BoxComponent) MarshalJSON() ([]byte, error) {
 		JustifyContent  FlexComponentJustifyContentType `json:"justifyContent,omitempty"`
 		AlignItems      FlexComponentAlignItemsType     `json:"alignItems,omitempty"`
 		Background      *BoxBackground                  `json:"background,omitempty"`
+		OffsetTop       FlexComponentOffsetType         `json:"offsetTop,omitempty"`
+		OffsetBottom    FlexComponentOffsetType         `json:"offsetBottom,omitempty"`
+		OffsetStart     FlexComponentOffsetType         `json:"offsetStart,omitempty"`
+		OffsetEnd       FlexComponentOffsetType         `json:"offsetEnd,omitempty"`
+		PaddingAll      FlexComponentPaddingType        `json:"paddingAll,omitempty"`
+		PaddingTop      FlexComponentPaddingType        `json:"paddingTop,omitempty"`
+		PaddingBottom   FlexComponentPaddingType        `json:"paddingBottom,omitempty"`
+		PaddingStart    FlexComponentPaddingType        `json:"paddingStart,omitempty"`
+		PaddingEnd      FlexComponentPaddingType        `json:"paddingEnd,omitempty"`
 	}{
 		Type:            FlexComponentTypeBox,
 		Layout:          c.Layout,
+		Position:        c.Position,
 		Contents:        c.Contents,
 		Flex:            c.Flex,
 		Spacing:         c.Spacing,
@@ -490,6 +548,15 @@ func (c *BoxComponent) MarshalJSON() ([]byte, error) {
 		JustifyContent:  c.JustifyContent,
 		AlignItems:      c.AlignItems,
 		Background:      c.Background,
+		OffsetTop:       c.OffsetTop,
+		OffsetBottom:    c.OffsetBottom,
+		OffsetStart:     c.OffsetStart,
+		OffsetEnd:       c.OffsetEnd,
+		PaddingAll:      c.PaddingAll,
+		PaddingTop:      c.PaddingTop,
+		PaddingBottom:   c.PaddingBottom,
+		PaddingStart:    c.PaddingStart,
+		PaddingEnd:      c.PaddingEnd,
 	})
 }
 
@@ -505,39 +572,54 @@ type BoxBackground struct {
 
 // ButtonComponent type
 type ButtonComponent struct {
-	Type       FlexComponentType
-	Action     TemplateAction
-	Flex       *int
-	Margin     FlexComponentMarginType
-	Height     FlexButtonHeightType
-	Style      FlexButtonStyleType
-	Color      string
-	Gravity    FlexComponentGravityType
-	AdjustMode FlexComponentAdjustModeType
+	Type         FlexComponentType
+	Action       TemplateAction
+	Position     FlexComponentPositionType
+	Flex         *int
+	Margin       FlexComponentMarginType
+	Height       FlexButtonHeightType
+	Style        FlexButtonStyleType
+	Color        string
+	Gravity      FlexComponentGravityType
+	AdjustMode   FlexComponentAdjustModeType
+	OffsetTop    FlexComponentOffsetType
+	OffsetBottom FlexComponentOffsetType
+	OffsetStart  FlexComponentOffsetType
+	OffsetEnd    FlexComponentOffsetType
 }
 
 // MarshalJSON method of ButtonComponent
 func (c *ButtonComponent) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&struct {
-		Type       FlexComponentType           `json:"type"`
-		Action     TemplateAction              `json:"action"`
-		Flex       *int                        `json:"flex,omitempty"`
-		Margin     FlexComponentMarginType     `json:"margin,omitempty"`
-		Height     FlexButtonHeightType        `json:"height,omitempty"`
-		Style      FlexButtonStyleType         `json:"style,omitempty"`
-		Color      string                      `json:"color,omitempty"`
-		Gravity    FlexComponentGravityType    `json:"gravity,omitempty"`
-		AdjustMode FlexComponentAdjustModeType `json:"adjustMode,omitempty"`
+		Type         FlexComponentType           `json:"type"`
+		Action       TemplateAction              `json:"action"`
+		Position     FlexComponentPositionType   `json:"position,omitempty"`
+		Flex         *int                        `json:"flex,omitempty"`
+		Margin       FlexComponentMarginType     `json:"margin,omitempty"`
+		Height       FlexButtonHeightType        `json:"height,omitempty"`
+		Style        FlexButtonStyleType         `json:"style,omitempty"`
+		Color        string                      `json:"color,omitempty"`
+		Gravity      FlexComponentGravityType    `json:"gravity,omitempty"`
+		AdjustMode   FlexComponentAdjustModeType `json:"adjustMode,omitempty"`
+		OffsetTop    FlexComponentOffsetType     `json:"offsetTop,omitempty"`
+		OffsetBottom FlexComponentOffsetType     `json:"offsetBottom,omitempty"`
+		OffsetStart  FlexComponentOffsetType     `json:"offsetStart,omitempty"`
+		OffsetEnd    FlexComponentOffsetType     `json:"offsetEnd,omitempty"`
 	}{
-		Type:       FlexComponentTypeButton,
-		Action:     c.Action,
-		Flex:       c.Flex,
-		Margin:     c.Margin,
-		Height:     c.Height,
-		Style:      c.Style,
-		Color:      c.Color,
-		Gravity:    c.Gravity,
-		AdjustMode: c.AdjustMode,
+		Type:         FlexComponentTypeButton,
+		Action:       c.Action,
+		Position:     c.Position,
+		Flex:         c.Flex,
+		Margin:       c.Margin,
+		Height:       c.Height,
+		Style:        c.Style,
+		Color:        c.Color,
+		Gravity:      c.Gravity,
+		AdjustMode:   c.AdjustMode,
+		OffsetTop:    c.OffsetTop,
+		OffsetBottom: c.OffsetBottom,
+		OffsetStart:  c.OffsetStart,
+		OffsetEnd:    c.OffsetEnd,
 	})
 }
 
@@ -588,6 +670,7 @@ func (c *IconComponent) MarshalJSON() ([]byte, error) {
 type ImageComponent struct {
 	Type            FlexComponentType
 	URL             string
+	Position        FlexComponentPositionType
 	Flex            *int
 	Margin          FlexComponentMarginType
 	Align           FlexComponentAlignType
@@ -598,26 +681,36 @@ type ImageComponent struct {
 	BackgroundColor string
 	Action          TemplateAction
 	Animated        bool
+	OffsetTop       FlexComponentOffsetType
+	OffsetBottom    FlexComponentOffsetType
+	OffsetStart     FlexComponentOffsetType
+	OffsetEnd       FlexComponentOffsetType
 }
 
 // MarshalJSON method of ImageComponent
 func (c *ImageComponent) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&struct {
-		Type            FlexComponentType        `json:"type"`
-		URL             string                   `json:"url"`
-		Flex            *int                     `json:"flex,omitempty"`
-		Margin          FlexComponentMarginType  `json:"margin,omitempty"`
-		Align           FlexComponentAlignType   `json:"align,omitempty"`
-		Gravity         FlexComponentGravityType `json:"gravity,omitempty"`
-		Size            FlexImageSizeType        `json:"size,omitempty"`
-		AspectRatio     FlexImageAspectRatioType `json:"aspectRatio,omitempty"`
-		AspectMode      FlexImageAspectModeType  `json:"aspectMode,omitempty"`
-		BackgroundColor string                   `json:"backgroundColor,omitempty"`
-		Action          TemplateAction           `json:"action,omitempty"`
-		Animated        bool                     `json:"animated,omitempty"`
+		Type            FlexComponentType         `json:"type"`
+		URL             string                    `json:"url"`
+		Position        FlexComponentPositionType `json:"position,omitempty"`
+		Flex            *int                      `json:"flex,omitempty"`
+		Margin          FlexComponentMarginType   `json:"margin,omitempty"`
+		Align           FlexComponentAlignType    `json:"align,omitempty"`
+		Gravity         FlexComponentGravityType  `json:"gravity,omitempty"`
+		Size            FlexImageSizeType         `json:"size,omitempty"`
+		AspectRatio     FlexImageAspectRatioType  `json:"aspectRatio,omitempty"`
+		AspectMode      FlexImageAspectModeType   `json:"aspectMode,omitempty"`
+		BackgroundColor string                    `json:"backgroundColor,omitempty"`
+		Action          TemplateAction            `json:"action,omitempty"`
+		Animated        bool                      `json:"animated,omitempty"`
+		OffsetTop       FlexComponentOffsetType   `json:"offsetTop,omitempty"`
+		OffsetBottom    FlexComponentOffsetType   `json:"offsetBottom,omitempty"`
+		OffsetStart     FlexComponentOffsetType   `json:"offsetStart,omitempty"`
+		OffsetEnd       FlexComponentOffsetType   `json:"offsetEnd,omitempty"`
 	}{
 		Type:            FlexComponentTypeImage,
 		URL:             c.URL,
+		Position:        c.Position,
 		Flex:            c.Flex,
 		Margin:          c.Margin,
 		Align:           c.Align,
@@ -628,6 +721,10 @@ func (c *ImageComponent) MarshalJSON() ([]byte, error) {
 		BackgroundColor: c.BackgroundColor,
 		Action:          c.Action,
 		Animated:        c.Animated,
+		OffsetTop:       c.OffsetTop,
+		OffsetBottom:    c.OffsetBottom,
+		OffsetStart:     c.OffsetStart,
+		OffsetEnd:       c.OffsetEnd,
 	})
 }
 
@@ -702,60 +799,75 @@ func (c *SpanComponent) MarshalJSON() ([]byte, error) {
 
 // TextComponent type
 type TextComponent struct {
-	Type       FlexComponentType
-	Text       string
-	Contents   []*SpanComponent
-	Flex       *int
-	Margin     FlexComponentMarginType
-	Size       FlexTextSizeType
-	Align      FlexComponentAlignType
-	Gravity    FlexComponentGravityType
-	Wrap       bool
-	Weight     FlexTextWeightType
-	Color      string
-	Action     TemplateAction
-	Style      FlexTextStyleType
-	Decoration FlexTextDecorationType
-	MaxLines   *int
-	AdjustMode FlexComponentAdjustModeType
+	Type         FlexComponentType
+	Text         string
+	Position     FlexComponentPositionType
+	Contents     []*SpanComponent
+	Flex         *int
+	Margin       FlexComponentMarginType
+	Size         FlexTextSizeType
+	Align        FlexComponentAlignType
+	Gravity      FlexComponentGravityType
+	Wrap         bool
+	Weight       FlexTextWeightType
+	Color        string
+	Action       TemplateAction
+	Style        FlexTextStyleType
+	Decoration   FlexTextDecorationType
+	MaxLines     *int
+	AdjustMode   FlexComponentAdjustModeType
+	OffsetTop    FlexComponentOffsetType
+	OffsetBottom FlexComponentOffsetType
+	OffsetStart  FlexComponentOffsetType
+	OffsetEnd    FlexComponentOffsetType
 }
 
 // MarshalJSON method of TextComponent
 func (c *TextComponent) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&struct {
-		Type       FlexComponentType           `json:"type"`
-		Text       string                      `json:"text,omitempty"`
-		Contents   []*SpanComponent            `json:"contents,omitempty"`
-		Flex       *int                        `json:"flex,omitempty"`
-		Margin     FlexComponentMarginType     `json:"margin,omitempty"`
-		Size       FlexTextSizeType            `json:"size,omitempty"`
-		Align      FlexComponentAlignType      `json:"align,omitempty"`
-		Gravity    FlexComponentGravityType    `json:"gravity,omitempty"`
-		Wrap       bool                        `json:"wrap,omitempty"`
-		Weight     FlexTextWeightType          `json:"weight,omitempty"`
-		Color      string                      `json:"color,omitempty"`
-		Action     TemplateAction              `json:"action,omitempty"`
-		Style      FlexTextStyleType           `json:"style,omitempty"`
-		Decoration FlexTextDecorationType      `json:"decoration,omitempty"`
-		MaxLines   *int                        `json:"maxLines,omitempty"`
-		AdjustMode FlexComponentAdjustModeType `json:"adjustMode,omitempty"`
+		Type         FlexComponentType           `json:"type"`
+		Text         string                      `json:"text,omitempty"`
+		Position     FlexComponentPositionType   `json:"position,omitempty"`
+		Contents     []*SpanComponent            `json:"contents,omitempty"`
+		Flex         *int                        `json:"flex,omitempty"`
+		Margin       FlexComponentMarginType     `json:"margin,omitempty"`
+		Size         FlexTextSizeType            `json:"size,omitempty"`
+		Align        FlexComponentAlignType      `json:"align,omitempty"`
+		Gravity      FlexComponentGravityType    `json:"gravity,omitempty"`
+		Wrap         bool                        `json:"wrap,omitempty"`
+		Weight       FlexTextWeightType          `json:"weight,omitempty"`
+		Color        string                      `json:"color,omitempty"`
+		Action       TemplateAction              `json:"action,omitempty"`
+		Style        FlexTextStyleType           `json:"style,omitempty"`
+		Decoration   FlexTextDecorationType      `json:"decoration,omitempty"`
+		MaxLines     *int                        `json:"maxLines,omitempty"`
+		AdjustMode   FlexComponentAdjustModeType `json:"adjustMode,omitempty"`
+		OffsetTop    FlexComponentOffsetType     `json:"offsetTop,omitempty"`
+		OffsetBottom FlexComponentOffsetType     `json:"offsetBottom,omitempty"`
+		OffsetStart  FlexComponentOffsetType     `json:"offsetStart,omitempty"`
+		OffsetEnd    FlexComponentOffsetType     `json:"offsetEnd,omitempty"`
 	}{
-		Type:       FlexComponentTypeText,
-		Text:       c.Text,
-		Contents:   c.Contents,
-		Flex:       c.Flex,
-		Margin:     c.Margin,
-		Size:       c.Size,
-		Align:      c.Align,
-		Gravity:    c.Gravity,
-		Wrap:       c.Wrap,
-		Weight:     c.Weight,
-		Color:      c.Color,
-		Action:     c.Action,
-		Style:      c.Style,
-		Decoration: c.Decoration,
-		MaxLines:   c.MaxLines,
-		AdjustMode: c.AdjustMode,
+		Type:         FlexComponentTypeText,
+		Text:         c.Text,
+		Position:     c.Position,
+		Contents:     c.Contents,
+		Flex:         c.Flex,
+		Margin:       c.Margin,
+		Size:         c.Size,
+		Align:        c.Align,
+		Gravity:      c.Gravity,
+		Wrap:         c.Wrap,
+		Weight:       c.Weight,
+		Color:        c.Color,
+		Action:       c.Action,
+		Style:        c.Style,
+		Decoration:   c.Decoration,
+		MaxLines:     c.MaxLines,
+		AdjustMode:   c.AdjustMode,
+		OffsetTop:    c.OffsetTop,
+		OffsetBottom: c.OffsetBottom,
+		OffsetStart:  c.OffsetStart,
+		OffsetEnd:    c.OffsetEnd,
 	})
 }
 


Test patch:
diff --git a/linebot/flex_test.go b/linebot/flex_test.go
index 619d0a2..cade97e 100644
--- a/linebot/flex_test.go
+++ b/linebot/flex_test.go
@@ -28,21 +28,21 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 	}{
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "body": {
-    "type": "box",
-    "layout": "vertical",
-    "contents": [
-      {
-        "type": "text",
-        "text": "hello"
-      },
-      {
-        "type": "text",
-        "text": "world"
-      }
-    ]
-  }
+	"type": "bubble",
+	"body": {
+		"type": "box",
+		"layout": "vertical",
+		"contents": [
+			{
+				"type": "text",
+				"text": "hello"
+			},
+			{
+				"type": "text",
+				"text": "world"
+			}
+		]
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,
@@ -64,39 +64,39 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 		},
 		{
 			JSON: []byte(`{
-  "type": "carousel",
-  "contents": [
-    {
-      "type": "bubble",
-      "body": {
-        "type": "box",
-        "layout": "vertical",
-        "contents": [
-          {
-            "type": "text",
-            "text": "First bubble"
-          }
-        ]
-      }
-    },
-    {
-      "type": "bubble",
-      "body": {
-        "type": "box",
-        "layout": "vertical",
-        "contents": [
-          {
-            "type": "text",
-            "text": "Second bubble"
-          }
-        ],
-        "action": {
-          "type": "message",
-          "text": "Second bubble"
-        }
-      }
-    }
-  ]
+	"type": "carousel",
+	"contents": [
+		{
+			"type": "bubble",
+			"body": {
+				"type": "box",
+				"layout": "vertical",
+				"contents": [
+					{
+						"type": "text",
+						"text": "First bubble"
+					}
+				]
+			}
+		},
+		{
+			"type": "bubble",
+			"body": {
+				"type": "box",
+				"layout": "vertical",
+				"contents": [
+					{
+						"type": "text",
+						"text": "Second bubble"
+					}
+				],
+				"action": {
+					"type": "message",
+					"text": "Second bubble"
+				}
+			}
+		}
+	]
 }`),
 			Want: &CarouselContainer{
 				Type: FlexContainerTypeCarousel,
@@ -133,22 +133,22 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 		},
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "size": "nano",
-  "body": {
-    "type": "box",
-    "layout": "vertical",
-    "contents": [
-      {
-        "type": "text",
-        "text": "hello"
-      },
-      {
-        "type": "text",
-        "text": "world"
-      }
-    ]
-  }
+	"type": "bubble",
+	"size": "nano",
+	"body": {
+		"type": "box",
+		"layout": "vertical",
+		"contents": [
+			{
+				"type": "text",
+				"text": "hello"
+			},
+			{
+				"type": "text",
+				"text": "world"
+			}
+		]
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,
@@ -171,179 +171,204 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 		},
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "hero": {
-    "type": "image",
-    "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png",
-    "size": "full",
-    "aspectRatio": "20:13",
-    "aspectMode": "cover",
-    "action": {
-      "type": "uri",
-      "uri": "https://linecorp.com/"
-    },
-    "animated": true
-  },
-  "body": {
-    "type": "box",
-    "layout": "vertical",
-    "contents": [
-      {
-        "type": "text",
-        "text": "Brown Cafe",
-        "weight": "bold",
-        "size": "xl"
-      },
-      {
-        "type": "box",
-        "layout": "baseline",
-        "margin": "md",
-        "contents": [
-          {
-            "type": "icon",
-            "size": "sm",
-            "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
-          },
-          {
-            "type": "icon",
-            "size": "sm",
-            "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
-          },
-          {
-            "type": "icon",
-            "size": "sm",
-            "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
-          },
-          {
-            "type": "icon",
-            "size": "sm",
-            "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
-          },
-          {
-            "type": "icon",
-            "size": "sm",
-            "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png"
-          },
-          {
-            "type": "text",
-            "text": "4.0",
-            "size": "sm",
-            "color": "#999999",
-            "margin": "md",
-            "flex": 0
-          }
-        ]
-      },
-      {
-        "type": "box",
-        "layout": "vertical",
-        "margin": "lg",
-        "spacing": "sm",
-        "contents": [
-          {
-            "type": "box",
-            "layout": "baseline",
-            "spacing": "sm",
-            "contents": [
-              {
-                "type": "text",
-                "text": "Place",
-                "color": "#aaaaaa",
-                "size": "sm",
-                "flex": 1,
-                "maxLines": 0
-              },
-              {
-                "type": "text",
-                "text": "Miraina Tower, 4-1-6 Shinjuku, Tokyo",
-                "wrap": true,
-                "color": "#666666",
-                "size": "sm",
-                "flex": 5,
-                "maxLines": 1
-              }
-            ]
-          },
-          {
-            "type": "box",
-            "layout": "baseline",
-            "spacing": "sm",
-            "contents": [
-              {
-                "type": "text",
-                "text": "Time",
-                "color": "#aaaaaa",
-                "size": "sm",
-                "flex": 1
-              },
-              {
-                "type": "text",
-                "text": "10:00 - 23:00",
-                "wrap": true,
-                "color": "#666666",
-                "size": "sm",
-                "flex": 5
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  },
-  "footer": {
-    "type": "box",
-    "layout": "vertical",
-    "spacing": "sm",
-    "contents": [
-      {
-        "type": "button",
-        "style": "link",
-        "height": "sm",
-        "action": {
-          "type": "uri",
-          "label": "CALL",
-          "uri": "https://linecorp.com"
-        }
-      },
-      {
-        "type": "button",
-        "style": "link",
-        "height": "sm",
-        "action": {
-          "type": "uri",
-          "label": "WEBSITE",
-          "uri": "https://linecorp.com",
-          "altUri": {
-            "desktop": "https://line.me/ja/download"
-          }
-        },
-        "adjustMode": "shrink-to-fit"
-      },
-      {
-        "type": "spacer",
-        "size": "sm"
-      },
-      {
-        "type": "spacer"
-      },
-      {
-        "type": "spacer",
-        "size": "xs"
-      }
-    ],
-    "flex": 0
-  }
+	"type": "bubble",
+	"hero": {
+		"type": "image",
+		"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png",
+		"position": "absolute",
+		"size": "full",
+		"aspectRatio": "20:13",
+		"aspectMode": "cover",
+		"action": {
+			"type": "uri",
+			"uri": "https://linecorp.com/"
+		},
+		"animated": true,
+		"offsetTop": "xs",
+		"offsetBottom": "sm",
+		"offsetStart": "md",
+		"offsetEnd": "lg"
+	},
+	"body": {
+		"type": "box",
+		"layout": "vertical",
+		"contents": [
+			{
+				"type": "text",
+				"text": "Brown Cafe",
+				"weight": "bold",
+				"size": "xl"
+			},
+			{
+				"type": "box",
+				"layout": "baseline",
+				"margin": "md",
+				"contents": [
+					{
+						"type": "icon",
+						"size": "sm",
+						"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
+					},
+					{
+						"type": "icon",
+						"size": "sm",
+						"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
+					},
+					{
+						"type": "icon",
+						"size": "sm",
+						"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
+					},
+					{
+						"type": "icon",
+						"size": "sm",
+						"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"
+					},
+					{
+						"type": "icon",
+						"size": "sm",
+						"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png"
+					},
+					{
+						"type": "text",
+						"text": "4.0",
+						"size": "sm",
+						"color": "#999999",
+						"margin": "md",
+						"flex": 0
+					}
+				]
+			},
+			{
+				"type": "box",
+				"layout": "vertical",
+				"margin": "lg",
+				"spacing": "sm",
+				"contents": [
+					{
+						"type": "box",
+						"layout": "baseline",
+						"spacing": "sm",
+						"contents": [
+							{
+								"type": "text",
+								"text": "Place",
+								"color": "#aaaaaa",
+								"size": "sm",
+								"flex": 1,
+								"maxLines": 0
+							},
+							{
+								"type": "text",
+								"text": "Miraina Tower, 4-1-6 Shinjuku, Tokyo",
+								"wrap": true,
+								"color": "#666666",
+								"size": "sm",
+								"flex": 5,
+								"maxLines": 1
+							}
+						]
+					},
+					{
+						"type": "box",
+						"layout": "baseline",
+						"spacing": "sm",
+						"contents": [
+							{
+								"type": "text",
+								"text": "Time",
+								"color": "#aaaaaa",
+								"size": "sm",
+								"flex": 1
+							},
+							{
+								"type": "text",
+								"text": "10:00 - 23:00",
+								"position": "absolute",
+								"wrap": true,
+								"color": "#666666",
+								"size": "sm",
+								"flex": 5,
+								"offsetTop": "xs",
+								"offsetBottom": "sm",
+								"offsetStart": "md",
+								"offsetEnd": "lg"
+							}
+						]
+					}
+				]
+			}
+		]
+	},
+	"footer": {
+		"type": "box",
+		"layout": "vertical",
+		"position": "absolute",
+		"spacing": "sm",
+		"contents": [
+			{
+				"type": "button",
+				"style": "link",
+				"height": "sm",
+				"action": {
+					"type": "uri",
+					"label": "CALL",
+					"uri": "https://linecorp.com"
+				},
+				"position": "absolute",
+				"offsetTop": "xs",
+				"offsetBottom": "sm",
+				"offsetStart": "md",
+				"offsetEnd": "lg"
+			},
+			{
+				"type": "button",
+				"style": "link",
+				"height": "sm",
+				"action": {
+					"type": "uri",
+					"label": "WEBSITE",
+					"uri": "https://linecorp.com",
+					"altUri": {
+						"desktop": "https://line.me/ja/download"
+					}
+				},
+				"adjustMode": "shrink-to-fit"
+			},
+			{
+				"type": "spacer",
+				"size": "sm"
+			},
+			{
+				"type": "spacer"
+			},
+			{
+				"type": "spacer",
+				"size": "xs"
+			}
+		],
+		"flex": 0,
+		"offsetTop": "xs",
+		"offsetBottom": "sm",
+		"offsetStart": "md",
+		"offsetEnd": "lg"
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,
 				Hero: &ImageComponent{
-					Type:        FlexComponentTypeImage,
-					URL:         "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png",
-					Size:        FlexImageSizeTypeFull,
-					AspectRatio: FlexImageAspectRatioType20to13,
-					AspectMode:  FlexImageAspectModeTypeCover,
-					Action:      &URIAction{URI: "https://linecorp.com/"},
-					Animated:    true,
+					Type:         FlexComponentTypeImage,
+					URL:          "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png",
+					Position:     FlexComponentPositionTypeAbsolute,
+					Size:         FlexImageSizeTypeFull,
+					AspectRatio:  FlexImageAspectRatioType20to13,
+					AspectMode:   FlexImageAspectModeTypeCover,
+					Action:       &URIAction{URI: "https://linecorp.com/"},
+					Animated:     true,
+					OffsetTop:    FlexComponentOffsetTypeXs,
+					OffsetBottom: FlexComponentOffsetTypeSm,
+					OffsetStart:  FlexComponentOffsetTypeMd,
+					OffsetEnd:    FlexComponentOffsetTypeLg,
 				},
 				Body: &BoxComponent{
 					Type:   FlexComponentTypeBox,
@@ -435,12 +460,17 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 											Color: "#aaaaaa",
 										},
 										&TextComponent{
-											Type:  FlexComponentTypeText,
-											Text:  "10:00 - 23:00",
-											Flex:  IntPtr(5),
-											Size:  FlexTextSizeTypeSm,
-											Wrap:  true,
-											Color: "#666666",
+											Type:         FlexComponentTypeText,
+											Text:         "10:00 - 23:00",
+											Position:     FlexComponentPositionTypeAbsolute,
+											Flex:         IntPtr(5),
+											Size:         FlexTextSizeTypeSm,
+											Wrap:         true,
+											Color:        "#666666",
+											OffsetTop:    FlexComponentOffsetTypeXs,
+											OffsetBottom: FlexComponentOffsetTypeSm,
+											OffsetStart:  FlexComponentOffsetTypeMd,
+											OffsetEnd:    FlexComponentOffsetTypeLg,
 										},
 									},
 									Spacing: FlexComponentSpacingTypeSm,
@@ -452,8 +482,9 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 					},
 				},
 				Footer: &BoxComponent{
-					Type:   FlexComponentTypeBox,
-					Layout: FlexBoxLayoutTypeVertical,
+					Type:     FlexComponentTypeBox,
+					Layout:   FlexBoxLayoutTypeVertical,
+					Position: FlexComponentPositionTypeAbsolute,
 					Contents: []FlexComponent{
 						&ButtonComponent{
 							Type: FlexComponentTypeButton,
@@ -461,8 +492,13 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 								Label: "CALL",
 								URI:   "https://linecorp.com",
 							},
-							Height: FlexButtonHeightTypeSm,
-							Style:  FlexButtonStyleTypeLink,
+							Position:     FlexComponentPositionTypeAbsolute,
+							Height:       FlexButtonHeightTypeSm,
+							Style:        FlexButtonStyleTypeLink,
+							OffsetTop:    FlexComponentOffsetTypeXs,
+							OffsetBottom: FlexComponentOffsetTypeSm,
+							OffsetStart:  FlexComponentOffsetTypeMd,
+							OffsetEnd:    FlexComponentOffsetTypeLg,
 						},
 						&ButtonComponent{
 							Type: FlexComponentTypeButton,
@@ -489,49 +525,53 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 							Size: FlexSpacerSizeTypeXs,
 						},
 					},
-					Spacing: FlexComponentSpacingTypeSm,
-					Flex:    IntPtr(0),
+					Spacing:      FlexComponentSpacingTypeSm,
+					Flex:         IntPtr(0),
+					OffsetTop:    FlexComponentOffsetTypeXs,
+					OffsetBottom: FlexComponentOffsetTypeSm,
+					OffsetStart:  FlexComponentOffsetTypeMd,
+					OffsetEnd:    FlexComponentOffsetTypeLg,
 				},
 			},
 		},
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "body": {
-    "type": "box",
-    "layout": "horizontal",
-    "contents": [
-      {
-        "type": "text",
-        "text": "hello",
-        "flex": 0
-      },
-      {
-        "type": "filler",
-        "flex": 4
-      },
-      {
-        "type": "text",
-        "text": "world",
-        "flex": 2
-	  },
-	  {
-        "type": "text",
-        "contents": [
-		  {
-			"type": "span",
-			"text": "hi"
-		  },
-		  {
-			"type": "span",
-			"text": "span",
-			"size": "xl",
-			"color": "#29cf5b"
-		  }
+	"type": "bubble",
+	"body": {
+		"type": "box",
+		"layout": "horizontal",
+		"contents": [
+			{
+				"type": "text",
+				"text": "hello",
+				"flex": 0
+			},
+			{
+				"type": "filler",
+				"flex": 4
+			},
+			{
+				"type": "text",
+				"text": "world",
+				"flex": 2
+			},
+			{
+				"type": "text",
+				"contents": [
+					{
+						"type": "span",
+						"text": "hi"
+					},
+					{
+						"type": "span",
+						"text": "span",
+						"size": "xl",
+						"color": "#29cf5b"
+					}
+				]
+			}
 		]
-      }
-    ]
-  }
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,
@@ -574,22 +614,22 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 		},
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "size": "nano",
-  "hero": {
-    "type": "box",
-    "layout": "vertical",
-    "contents": [
-      {
-        "type": "text",
-        "text": "hello"
-      },
-      {
-        "type": "text",
-        "text": "world"
-      }
-    ]
-  }
+	"type": "bubble",
+	"size": "nano",
+	"hero": {
+		"type": "box",
+		"layout": "vertical",
+		"contents": [
+			{
+				"type": "text",
+				"text": "hello"
+			},
+			{
+				"type": "text",
+				"text": "world"
+			}
+		]
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,
@@ -612,44 +652,44 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 		},
 		{
 			JSON: []byte(`{
-  "type": "bubble",
-  "body": {
-    "type": "box",
-    "layout": "vertical",
-    "contents": [
-      {
-        "type": "image",
-        "url": "https://example.com/flex/images/image.jpg",
-        "animated": true
-      },
-      {
-        "type": "separator"
-      },
-      {
-        "type": "text",
-        "text": "Text in the box",
-        "adjustMode": "shrink-to-fit"
-      },
-      {
-        "type": "box",
-        "layout": "vertical",
-        "contents": [],
-        "width": "30px",
-        "height": "30px",
-        "background": {
-          "type": "linearGradient",
-          "angle": "0deg",
-          "startColor": "#ff0000",
-          "centerColor": "#0000ff",
-          "endColor": "#00ff00",
-          "centerPosition": "10%"
-        }
-      }
-    ],
-    "height": "400px",
-    "justifyContent": "space-evenly",
-    "alignItems": "center"
-  }
+	"type": "bubble",
+	"body": {
+		"type": "box",
+		"layout": "vertical",
+		"contents": [
+			{
+				"type": "image",
+				"url": "https://example.com/flex/images/image.jpg",
+				"animated": true
+			},
+			{
+				"type": "separator"
+			},
+			{
+				"type": "text",
+				"text": "Text in the box",
+				"adjustMode": "shrink-to-fit"
+			},
+			{
+				"type": "box",
+				"layout": "vertical",
+				"contents": [],
+				"width": "30px",
+				"height": "30px",
+				"background": {
+					"type": "linearGradient",
+					"angle": "0deg",
+					"startColor": "#ff0000",
+					"centerColor": "#0000ff",
+					"endColor": "#00ff00",
+					"centerPosition": "10%"
+				}
+			}
+		],
+		"height": "400px",
+		"justifyContent": "space-evenly",
+		"alignItems": "center"
+	}
 }`),
 			Want: &BubbleContainer{
 				Type: FlexContainerTypeBubble,