Skip to content

Commit

Permalink
feat(react): decorator for msgToBotonic
Browse files Browse the repository at this point in the history
downgrade jest to 24.9 to avoid jestjs/jest#9531
  • Loading branch information
dpinol committed Feb 10, 2020
1 parent 23f969f commit 0051098
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 101 deletions.
17 changes: 15 additions & 2 deletions packages/botonic-react/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react'
import * as core from '@botonic/core'
import { ReactNode } from 'react'

export type MessageType = 'text' | 'carousel' | 'audio' | 'video' | 'location' | 'document' | 'buttonmessage' | 'custom' | 'image'

export interface MessageProps {
type?: string
Expand Down Expand Up @@ -102,5 +105,15 @@ export interface RequestContextInterface extends ActionRequest {

export const RequestContext: React.Context<RequestContextInterface>

export function msgToBotonic(msg: any): React.ReactNode
export function msgsToBotonic(msgs: any | any[]): React.ReactNode
export interface CustomMessageType {
customTypeName: string
}

export class BotonicDecorator {
addDecorator(type: MessageType, decorator: (node: React.ReactNode) => React.ReactNode)
decorate(type: MessageType, node: ReactNode): React.ReactNode
}


export function msgToBotonic(msg: any, customMessageTypes?: CustomMessageType[] , decorator?: BotonicDecorator): React.ReactNode
export function msgsToBotonic(msgs: any | any[], customMessageTypes?: CustomMessageType[] , decorator?: BotonicDecorator): React.ReactNode
2 changes: 1 addition & 1 deletion packages/botonic-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"babel-plugin-add-module-exports": "^1.0.0",
"eslint-plugin-no-null": "^1.0.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^25.1.0",
"jest": "24.9.0",
"react-hot-loader": "^4.12.19"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/botonic-react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export { ShareButton } from './components/share-button'
export { MessageTemplate } from './components/message-template'
export { customMessage } from './components/custom-message'
export { BotonicInputTester, BotonicOutputTester } from './botonicTester'
export { msgToBotonic, msgsToBotonic } from './msg-to-botonic'
export { msgToBotonic, msgsToBotonic, BotonicDecorator } from './msg-to-botonic'
export { staticAsset } from './utils'
91 changes: 74 additions & 17 deletions packages/botonic-react/src/msg-to-botonic.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,54 @@ import { Document } from './components/document'
import { Location } from './components/location'
import { Reply } from './components/reply'

export function msgToBotonic(msg, customMessageTypes) {
/**
* Decorates the ReactNode's created by msgToBotonic
*/
export class BotonicDecorator {
constructor() {
this.decorators = {}
}

/**
* @param type {MessageType}
* @param decorator {function}
* @return {void}
*/
addDecorator(type, decorator) {
this.decorators[type] = decorator
}

/**
* @param type {MessageType}
* @param component {React.Component}
* @param key {String|undeifned}
* @return {ReactNode}
*/
decorate(type, component, key) {
const decorator = this.decorators[type]
if (!decorator) {
return component
}
// https://reactjs.org/warnings/special-props.html
return decorator(component, key)
}
}

/**
*
* @param msg {object}
* @param customMessageTypes {{customTypeName}[]?}
* @param decorator {BotonicDecorator?}
* @return {React.ReactNode}
*/
export function msgToBotonic(msg, customMessageTypes, decorator) {
delete msg.display
const decorate = (type, node) =>
decorator ? decorator.decorate(type, node, msg.key) : node
if (msg.type === 'custom') {
try {
return customMessageTypes
.find(mt => mt.customTypeName == msg.data.customTypeName)
.find(mt => mt.customTypeName === msg.data.customTypeName)
.deserialize(msg)
} catch (e) {
console.log(e)
Expand All @@ -29,46 +71,54 @@ export function msgToBotonic(msg, customMessageTypes) {
(msg.replies && msg.replies.length) ||
(msg.keyboard && msg.keyboard.length)
)
return (
return decorate(
'text',
<Text {...msg}>
{txt}
{quickreplies_parse(msg)}
</Text>
)
if (msg.buttons && msg.buttons.length)
return (
return decorate(
'text',
<Text {...msg}>
{txt}
{buttons_parse(msg.buttons)}
</Text>
)
return <Text {...msg}>{txt}</Text>
return decorate('text', <Text {...msg}>{txt}</Text>)
} else if (msg.type === 'carousel') {
let elements = msg.elements || msg.data.elements
return <Carousel {...msg}>{elements_parse(elements)}</Carousel>
return decorate(
'carousel',
<Carousel {...msg}>{elements_parse(elements)}</Carousel>
)
} else if (msg.type === 'image') {
return (
return
return decorate(
'image',
<Image
{...msg}
src={msg.data.image != undefined ? msg.data.image : msg.data}
/>
)
} else if (msg.type === 'video') {
return (
return decorate(
'video',
<Video
{...msg}
src={msg.data.video != undefined ? msg.data.video : msg.data}
/>
)
} else if (msg.type === 'audio') {
return (
<Audio
return decorate(
'audio',<Audio
{...msg}
src={msg.data.audio != undefined ? msg.data.audio : msg.data}
/>
)
} else if (msg.type === 'document') {
return (
return decorate('document',
<Document
{...msg}
src={msg.data.document != undefined ? msg.data.document : msg.data}
Expand All @@ -82,29 +132,36 @@ export function msgToBotonic(msg, customMessageTypes) {
let buttons = buttons_parse(msg.buttons)
return (
<>
<Text {...msg}>
decorate('text', <Text {...msg}>
{msg.text}
{buttons}
</Text>
)
</>
)
}
}

export function msgsToBotonic(msgs, customMessageTypes) {
/**
* @param msgs {object|object[]}
* @param customMessageTypes {{customTypeName}[]?}
* @param decorator { BotonicDecorator?}
* @return {React.ReactNode}
*/
export function msgsToBotonic(msgs, customMessageTypes, decorator) {
if (Array.isArray(msgs)) {
return (
<>
{msgs.map((msg, i) => {
if (msg['key'] == null) {
msg['key'] = `msg${i}`
if (msg.key == null) {
msg.key = `msg${i}`
}
return msgToBotonic(msg, customMessageTypes)
return msgToBotonic(msg, customMessageTypes, decorator)
})}
</>
)
}
return msgToBotonic(msgs, customMessageTypes)
return msgToBotonic(msgs, customMessageTypes, decorator)
}

function elements_parse(elements) {
Expand Down
158 changes: 158 additions & 0 deletions packages/botonic-react/tests/msg-to-botonic.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from 'react'
import { BotonicDecorator, msgsToBotonic } from '../src/msg-to-botonic'
import { Text, Carousel, Reply, Button, Element, Pic, Subtitle, Title } from '../src'

describe('msgsToBotonic carousel', () => {

test('with pic, title & subtitle', () => {
const msg = {
type: 'carousel',
elements: [{
title: 'tit1',
subtitle: 'sub1',
pic: 'htp://pic',
buttons: [
{
payload: 'payload1',
title: 'button text',
},
],
}],
}
expect(msgsToBotonic(msg)).toEqual(
<Carousel {...msg}>
{[
<Element key={0}>
<Pic src="htp://pic"/>
<Title>tit1</Title>
<Subtitle>sub1</Subtitle>
{[
<Button key='0' payload='payload1'>
button text
</Button>,
]}
</Element>,
]}
</Carousel>,
)
})
})

describe('msgsToBotonic text', () => {

test('with replies', () => {
const msg = {
type: 'text',
data: {
text: 'The verbose text',
},
replies: [
{
payload: 'payload1',
text: 'reply text',
},
],
}
expect(msgsToBotonic(msg)).toEqual(
<Text {...msg}>
The verbose text
{[
<Reply key='0' payload='payload1'>
reply text
</Reply>,
]}
</Text>,
)
})

test('with buttons', () => {
const msg = {
type: 'text',
data: {
text: 'The verbose text',
},
buttons: [
{
payload: 'payload1',
title: 'button text',
},
],
}
expect(msgsToBotonic(msg)).toEqual(
<Text {...msg}>
The verbose text
{[
<Button key='0' payload='payload1'>
button text
</Button>,
]}
</Text>,
)
})

test('without replies nor buttons', () => {
// normal text
let msg = { type: 'text', data: { text: 'texto' } }
expect(msgsToBotonic(msg)).toEqual(<Text {...msg}>texto</Text>)

// no text
msg = { type: 'text', data: { text: '' }, replies: [] }
expect(msgsToBotonic(msg)).toEqual(<Text {...msg}>{''}</Text>)// empty replies field

msg = { type: 'text', data: { text: 'texto' }, replies: [] }
expect(msgsToBotonic(msg)).toEqual(<Text {...msg}>texto</Text>)
})

test('no text', () => {
let msg = { type: 'text', data: { text: '' } }
expect(msgsToBotonic(msg)).toEqual(<Text {...msg}>{''}</Text>)

msg = { type: 'text', data: 'no text field', replies: [] }
expect(msgsToBotonic(msg)).toEqual(<Text {...msg}>no text field</Text>)
})

test('array', () => {
let msg = { type: 'text', data: { text: '' } }
expect(msgsToBotonic([msg])).toEqual(<>{[<Text key={'msg0'} {...msg}>{''}</Text>]}</>)
})

test('text with button with decorator', () => {
let msg = {
prop1: 'val1',
type: 'text',
data: {
text: 'The verbose text',
},
buttons: [
{
url: 'http://...',
title: 'button text',
},
],
}
const decorator = new BotonicDecorator()
const swapChildren = (txt, key) => (
<Text {...txt.props} key={key}>
{txt.props.children[1]}
{txt.props.children[0]}
</Text>
)
decorator.addDecorator('text', swapChildren)

const botNode = msgsToBotonic([msg], null, decorator)
expect(botNode).toEqual(
<>
{[
<Text key={'msg0'} {...msg}>
{[
<Button key='0' url='http://...'>
button text
</Button>,
]}
The verbose text
</Text>,
]}
</>,
)
})
})
Loading

0 comments on commit 0051098

Please sign in to comment.