├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lib └── index.js ├── package.json └── spec ├── message-builder-spec.js └── support ├── jasmine-runner.js └── jasmine.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "defaults", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-console": "off", 13 | "semi": ["error", "never"], 14 | "indent": ["error", 2], 15 | "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}], 16 | "prefer-arrow-callback": "error" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | - 4.3.2 5 | - 5 6 | - 6 7 | - 7 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Slobodan Stojanović 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alexa Message Builder 2 | 3 | [![](https://travis-ci.org/stojanovic/alexa-message-builder.svg?branch=master)](https://travis-ci.org/stojanovic/alexa-message-builder) 4 | [![npm](https://img.shields.io/npm/v/alexa-message-builder.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/alexa-message-builder) 5 | [![npm](https://img.shields.io/npm/l/alexa-message-builder.svg?maxAge=2592000?style=plastic)](https://github.com/claudiajs/alexa-message-builder/blob/master/LICENSE) 6 | 7 | Simple message builder for Alexa response. 8 | 9 | ## Installation 10 | 11 | Alexa Message Builder is available as a node module on NPM. 12 | 13 | Install it by running: 14 | 15 | ```shell 16 | npm install alexa-message-builder --save 17 | ``` 18 | 19 | ## Usage 20 | 21 | After installing the package, require it in your code: 22 | 23 | ```javascript 24 | const AlexaMessageBuilder = require('alexa-message-builder') 25 | ``` 26 | 27 | or with `import`* syntax: 28 | 29 | ```javascript 30 | import AlexaMessageBuilder from 'alexa-message-builder' 31 | ``` 32 | 33 | \* `import` syntax is not supported in Node.js, you need to use additional library like Babel to make it work. 34 | 35 | After requiring it, you simply need to initialize the class, use any of available methods from the [documentation](#documentation) below and call `.get()` in the end. For Example: 36 | 37 | ```javascript 38 | const AlexaMessageBuilder = require('alexa-message-builder') 39 | 40 | const message = new AlexaMessageBuilder() 41 | .addText('Hello from Alexa') 42 | .get() 43 | ``` 44 | 45 | will return: 46 | 47 | ```json 48 | { 49 | "version": "1.0", 50 | "response": { 51 | "shouldEndSession": false, 52 | "outputSpeech": { 53 | "type": "PlainText", 54 | "ssml": "Hello from Alexa" 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | ## Motivation 61 | 62 | Building JSON responses manually is not fun and hard to read for a big JSON files. The main motivation for this message builder is to replace them with a simple and readable syntax. For example, instead of this JSON: 63 | ```json 64 | { 65 | "version": "1.0", 66 | "response": { 67 | "shouldEndSession": false, 68 | "outputSpeech" : { 69 | "type": "PlainText", 70 | "text": "Alexa message builder is a simple message builder for Alexa responses" 71 | }, 72 | "card": { 73 | "type": "Standard", 74 | "title": "Alexa Message Builder", 75 | "text": "Alexa message builder description", 76 | "image": { 77 | "smallImageUrl": "http://example.com/small-image-url.png", 78 | "largeImageUrl": "http://example.com/large-image-url.png" 79 | } 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | You can write following JavaScript code: 86 | ```javascript 87 | new AlexaMessageBuilder() 88 | .addText('Alexa message builder is a simple message builder for Alexa responses') 89 | .addStandardCard('Alexa Message Builder', 'Alexa message builder description', { 90 | smallImageUrl: 'http://example.com/small-image-url.png', 91 | largeImageUrl: 'http://example.com/large-image-url.png' 92 | }) 93 | .keepSession() 94 | .get() 95 | ``` 96 | 97 | Package can work with any Node.js project for building Alexa app. For example, it works perfectly with [Claudia Bot Builder](https://github.com/claudiajs/claudia-bot-builder): 98 | 99 | ```javascript 100 | const BotBuilder = require('claudia-bot-builder'), 101 | AlexaMessageBuilder = require('alexa-message-builder') 102 | 103 | module.exports = botBuilder(message => { 104 | return new AlexaMessageBuilder() 105 | .addText('Hello from Alexa') 106 | .get() 107 | }, { 108 | platforms: ['alexa'] 109 | }) 110 | ``` 111 | 112 | ## Documentation 113 | 114 | Alexa Message Builder is still not covering 100% of Alexa JSON response, but it covers the big part of it. Here's how it works: 115 | 116 | Require the package you previously installed from NPM: 117 | 118 | ```javascript 119 | const AlexaMessageBuilder = require('alexa-message-builder') 120 | ``` 121 | 122 | or with `import`* syntax: 123 | 124 | ```javascript 125 | import AlexaMessageBuilder from 'alexa-message-builder' 126 | ``` 127 | 128 | \* `import` syntax is not supported in Node.js, you need to use additional library like Babel to make it work. 129 | 130 | After requiring it, you simply need to initialize the class, use any of available methods from the [documentation](#documentation) below and call `.get()` in the end. For Example: 131 | 132 | ```javascript 133 | const AlexaMessageBuilder = require('alexa-message-builder') 134 | 135 | const message = new AlexaMessageBuilder() 136 | .addText('Hello from Alexa') 137 | .get() 138 | ``` 139 | 140 | will return: 141 | 142 | ```json 143 | { 144 | "version": "1.0", 145 | "response": { 146 | "shouldEndSession": false, 147 | "outputSpeech": { 148 | "type": "PlainText", 149 | "text": "Hello from Alexa" 150 | } 151 | } 152 | } 153 | ``` 154 | 155 | ### Add output speech 156 | 157 | This generates the speech that Alexa will say as a reply to your question or command. It can be used as a response to a [LaunchRequest or IntentRequest](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference). 158 | 159 | You can send either plain text or [Speech Synthesis Markup Language (SSML)](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference). 160 | 161 | #### Available methods 162 | 163 | - addText 164 | - addSSML 165 | 166 | **addText** method can receive a plain text and it returns a reference to `this` for chaining. 167 | 168 | Example: 169 | 170 | ```javascript 171 | new AlexaMessageBuilder() 172 | .addText('A text that Alexa will use as a response') 173 | .get() 174 | ``` 175 | 176 | This method will throw an error if `text` is not provided. 177 | 178 | **addSSML** method can receive a SSML message as a string and it returns a reference to `this` for chaining. 179 | 180 | Example: 181 | 182 | ```javascript 183 | new AlexaMessageBuilder() 184 | .addSSML('This output speech uses SSML.') 185 | .get() 186 | ``` 187 | 188 | This method will throw an error if `ssmlMessage` is not provided. 189 | 190 | ### Add reprompt 191 | 192 | Similar to the output speech, reprompt supports both text and SSML, and it can be used as a response to a [LaunchRequest or IntentRequest](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference). 193 | 194 | #### Available methods 195 | 196 | - addText 197 | - addSSML 198 | 199 | **addRepromptText** method can receive a plain text and it returns a reference to `this` for chaining. 200 | 201 | Example: 202 | 203 | ```javascript 204 | new AlexaMessageBuilder() 205 | .addRepromptText('A reprompt text that Alexa will use as a response') 206 | .get() 207 | ``` 208 | 209 | This method will throw an error if `text` is not provided. 210 | 211 | **addRepromptSSML** method can receive a SSML message as a string and it returns a reference to `this` for chaining. 212 | 213 | Example: 214 | 215 | ```javascript 216 | new AlexaMessageBuilder() 217 | .addRepromptSSML('This reprompt speech uses SSML.') 218 | .get() 219 | ``` 220 | 221 | This method will throw an error if `ssmlMessage` is not provided. 222 | 223 | ### Add cards 224 | 225 | Alexa supports 3 different types of the cards: Simple, Standard and LinkAccount. First two types are supported by this library. 226 | 227 | Cards can only be included when sending a response to a [LaunchRequest or IntentRequest](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference). 228 | 229 | #### Add Simple card 230 | 231 | Simple card is a card that contains a title and plain text content. 232 | 233 | **addSimpleCard** method can receive title and text and it returns a reference to `this` for chaining. 234 | 235 | Example: 236 | 237 | ```javascript 238 | new AlexaMessageBuilder() 239 | .addText('A text that Alexa will use as a response') 240 | .addSimpleCard('Card title', 'Card text') 241 | .get() 242 | ``` 243 | 244 | This method will throw an error if both `title` and `text` are not provided. 245 | 246 | #### Add Standard card 247 | 248 | Standard card is a card that contains a title, text content, and an image to display. 249 | 250 | **addStandardCard** method can receive title, text and image object, and it returns a reference to `this` for chaining. 251 | 252 | Example: 253 | 254 | ```javascript 255 | new AlexaMessageBuilder() 256 | .addText('A text that Alexa will use as a response') 257 | .addStandardCard('Card title', 'Card text', { 258 | smallImageUrl: 'http://example.com/small-image-url.png', 259 | largeImageUrl: 'http://example.com/large-image-url.png' 260 | }) 261 | .get() 262 | ``` 263 | 264 | This method will throw an error if `title`, `text` and `imageObject` are not provided. 265 | 266 | ### Keep the session opened 267 | 268 | Alexa session will be closed by default, if you want to keep it opened use `.keepSession()` method. 269 | 270 | **keepSession** method will keep the session opened. It doesn't require any params. 271 | 272 | Example: 273 | 274 | ```javascript 275 | new AlexaMessageBuilder() 276 | .addText('A text that Alexa will use as a response, and session will not be closed') 277 | .keepSession() 278 | .get() 279 | ``` 280 | 281 | ### Add session attributes 282 | 283 | Alexa also allows you to store some session attributes while the session is opened. To do so with a message builder use `.addSessionAttribute(key, value)` method. 284 | 285 | **addSessionAttribute** method can receive key and value and it returns a reference to `this` for chaining. Key needs to be a string and value can be in other types too. 286 | 287 | Example: 288 | 289 | ```javascript 290 | new AlexaMessageBuilder() 291 | .addText('A text that Alexa will use as a response, and session will not be closed') 292 | .addSessionAttribute('someKey', 1) 293 | .keepSession() 294 | .get() 295 | ``` 296 | 297 | ## TODO 298 | 299 | - [ ] Add directives 300 | - [ ] Add LinkAccount cards 301 | - [ ] Check for limits 302 | 303 | ## Contribute 304 | 305 | ### Folder structure 306 | 307 | The main body of code is in the [lib](lib) directory. 308 | 309 | The tests are in the [spec](spec) directory, and should follow the structure of the corresponding source files. All executable test file names should end with `-spec`, so they will be automatically picked up by `npm test`. Any additional project files, helper classes etc that must not be directly executed by the test runner should not end with `-spec`. You can use the [spec/helpers](spec/helpers) directory to store Jasmine helpers, that will be loaded before any test is executed. 310 | 311 | ### Running tests 312 | 313 | We use [Jasmine](https://jasmine.github.io/) for unit and integration tests. Unless there is a very compelling reason to use something different, please continue using Jasmine for tests. The existing tests are in the [spec](spec) folder. Here are some useful command shortcuts: 314 | 315 | Run all the tests: 316 | 317 | ```bash 318 | npm test 319 | ``` 320 | 321 | Run only some tests: 322 | 323 | ```bash 324 | npm test -- filter=prefix 325 | ``` 326 | 327 | Get detailed hierarchical test name reporting: 328 | 329 | ```bash 330 | npm test -- full 331 | ``` 332 | 333 | We use [ESLint](http://eslint.org/) for syntax consistency, and the linting rules are included in this repository. Running `npm test` will check the linting rules as well. Please make sure your code has no linting errors before submitting a pull request. 334 | 335 | ## License 336 | 337 | MIT - See [LICENSE](LICENSE) 338 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib') 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class AlexaMessageBuilder { 4 | constructor() { 5 | this.template = { 6 | version: '1.0', 7 | response: { 8 | shouldEndSession: true 9 | } 10 | } 11 | } 12 | 13 | addVersion(versionString) { 14 | if (typeof versionString !== 'string') 15 | throw new Error('You need to provide version as a string for addVersion method, ie. "1.0"') 16 | 17 | this.template.version = versionString 18 | 19 | return this 20 | } 21 | 22 | addSessionAttribute(key, value) { 23 | if (typeof key !== 'string' || typeof value === 'undefined') 24 | throw new Error('You need to provide both key and value for addSessionAttribute method') 25 | 26 | if (!this.template.sessionAttributes) 27 | this.template.sessionAttributes = {} 28 | 29 | this.template.sessionAttributes[key] = value 30 | 31 | return this 32 | } 33 | 34 | addOutputSpeech(type, text, isReprompt) { 35 | if (['PlainText', 'SSML'].indexOf(type) < 0) 36 | throw new Error('You need to provide type and it can be either "PlainText" or "SSML" for addOutputSpeech method') 37 | 38 | if (typeof text !== 'string') 39 | throw new Error('You need to provide text as a string for addText, addSSML and addOutputSpeech methods') 40 | 41 | if (typeof this.template.response.outputSpeech === 'object' && !isReprompt) 42 | throw new Error('You can call addText or addSSML only once') 43 | 44 | let obj = this.template.response 45 | if (isReprompt) { 46 | this.template.response.reprompt = {} 47 | obj = obj.reprompt 48 | } 49 | 50 | obj.outputSpeech = { 51 | type: type 52 | } 53 | 54 | obj.outputSpeech[type === 'SSML' ? 'ssml' : 'text'] = text 55 | 56 | return this 57 | } 58 | 59 | addText(text) { 60 | return this.addOutputSpeech('PlainText', text) 61 | } 62 | 63 | addSSML(ssmlString) { 64 | return this.addOutputSpeech('SSML', ssmlString) 65 | } 66 | 67 | addRepromptText(text) { 68 | return this.addOutputSpeech('PlainText', text, true) 69 | } 70 | 71 | addRepromptSSML(ssmlString) { 72 | return this.addOutputSpeech('SSML', ssmlString, true) 73 | } 74 | 75 | addSimpleCard(title, content) { 76 | if (typeof title !== 'string' || typeof content !== 'string') 77 | throw new Error('You need to provide title and content as strings for addSimpleCard method') 78 | 79 | this.template.response.card = { 80 | type: 'Simple', 81 | title: title, 82 | content: content 83 | } 84 | 85 | return this 86 | } 87 | 88 | addStandardCard(title, text, imageObject) { 89 | this.template.response.card = { 90 | type: 'Standard', 91 | title: title, 92 | text: text 93 | } 94 | 95 | if (typeof imageObject === 'object' && (imageObject.smallImageUrl || imageObject.largeImageUrl)) 96 | this.template.response.card.image = imageObject 97 | 98 | return this 99 | } 100 | 101 | addDialogDelegate(updatedIntent) { 102 | if (!this.template.response.directives) { 103 | this.template.response.directives = [] 104 | } 105 | 106 | const directive = { 107 | type: 'Dialog.Delegate' 108 | } 109 | 110 | if (updatedIntent) { 111 | directive.updatedIntent = updatedIntent 112 | } 113 | 114 | this.template.response.directives.push(directive) 115 | 116 | return this 117 | } 118 | 119 | addDialogElicitSlot(slot, updatedIntent) { 120 | if (!this.template.response.directives) { 121 | this.template.response.directives = [] 122 | } 123 | 124 | const directive = { 125 | type: 'Dialog.ElicitSlot', 126 | slotToElicit: slot 127 | } 128 | 129 | if (updatedIntent) { 130 | directive.updatedIntent = updatedIntent 131 | } 132 | 133 | this.template.response.directives.push(directive) 134 | 135 | return this 136 | } 137 | 138 | addDialogConfirmSlot(slot, updatedIntent) { 139 | if (!this.template.response.directives) { 140 | this.template.response.directives = [] 141 | } 142 | 143 | const directive = { 144 | type: 'Dialog.ConfirmSlot', 145 | slotToConfirm: slot 146 | } 147 | 148 | if (updatedIntent) { 149 | directive.updatedIntent = updatedIntent 150 | } 151 | 152 | this.template.response.directives.push(directive) 153 | 154 | return this 155 | } 156 | 157 | addDialogConfirmIntent(updatedIntent) { 158 | if (!this.template.response.directives) { 159 | this.template.response.directives = [] 160 | } 161 | 162 | const directive = { 163 | type: 'Dialog.ConfirmIntent' 164 | } 165 | 166 | if (updatedIntent) { 167 | directive.updatedIntent = updatedIntent 168 | } 169 | 170 | this.template.response.directives.push(directive) 171 | 172 | return this 173 | } 174 | 175 | keepSession() { 176 | this.template.response.shouldEndSession = false 177 | 178 | return this 179 | } 180 | 181 | 182 | get() { 183 | return this.template 184 | } 185 | } 186 | 187 | module.exports = AlexaMessageBuilder 188 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-message-builder", 3 | "version": "1.1.0", 4 | "description": "Simple builder for Alexa replies.", 5 | "main": "index.js", 6 | "scripts": { 7 | "pretest": "eslint lib spec *.js", 8 | "test": "node spec/support/jasmine-runner.js", 9 | "debug": "node debug spec/support/jasmine-runner.js" 10 | }, 11 | "keywords": [ 12 | "alexa", 13 | "conversation", 14 | "message", 15 | "builder", 16 | "echo", 17 | "amazon" 18 | ], 19 | "author": "Slobodan Stojanovic (http://slobodan.me/)", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "eslint": "^3.15.0", 23 | "eslint-config-defaults": "^9.0.0", 24 | "jasmine": "^2.5.3", 25 | "jasmine-spec-reporter": "^3.2.0" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/stojanovic/alexa-message-builder.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/stojanovic/alexa-message-builder/issues" 33 | }, 34 | "homepage": "https://github.com/stojanovic/alexa-message-builder" 35 | } 36 | -------------------------------------------------------------------------------- /spec/message-builder-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | 'use strict' 3 | 4 | const AlexaMessageBuilder = require('../lib') 5 | 6 | describe('Alexa Message Builder', () => { 7 | it('should be a class', () => { 8 | const message = new AlexaMessageBuilder() 9 | expect(typeof AlexaMessageBuilder).toBe('function') 10 | expect(message instanceof AlexaMessageBuilder).toBeTruthy() 11 | }) 12 | 13 | it('should export an object when you invoke get method', () => { 14 | const message = new AlexaMessageBuilder().get() 15 | expect(message).toEqual({ 16 | version: '1.0', 17 | response: { 18 | shouldEndSession: true 19 | } 20 | }) 21 | }) 22 | 23 | describe('addVersion', () => { 24 | it('should be a function', () => { 25 | const message = new AlexaMessageBuilder() 26 | expect(typeof message.addVersion).toBe('function') 27 | }) 28 | 29 | it('should throw an error if version is not provided or not string', () => { 30 | const message = new AlexaMessageBuilder() 31 | 32 | expect(() => message.addVersion()).toThrowError('You need to provide version as a string for addVersion method, ie. "1.0"') 33 | expect(() => message.addVersion(1)).toThrowError('You need to provide version as a string for addVersion method, ie. "1.0"') 34 | expect(() => message.addVersion(1.0)).toThrowError('You need to provide version as a string for addVersion method, ie. "1.0"') 35 | expect(() => message.addVersion({})).toThrowError('You need to provide version as a string for addVersion method, ie. "1.0"') 36 | }) 37 | 38 | it('should set a version if it is provided', () => { 39 | const message = new AlexaMessageBuilder() 40 | 41 | expect(message.addVersion('2.0') instanceof AlexaMessageBuilder).toBeTruthy() 42 | expect(message.addVersion('2.0').get()).toEqual({ 43 | version: '2.0', 44 | response: { 45 | shouldEndSession: true 46 | } 47 | }) 48 | }) 49 | }) 50 | 51 | describe('addSessionAttribute', () => { 52 | it('should be a function', () => { 53 | const message = new AlexaMessageBuilder() 54 | expect(typeof message.addSessionAttribute).toBe('function') 55 | }) 56 | 57 | it('should throw an error if key is not string or value is not provided', () => { 58 | const message = new AlexaMessageBuilder() 59 | expect(() => message.addSessionAttribute()).toThrowError('You need to provide both key and value for addSessionAttribute method') 60 | expect(() => message.addSessionAttribute(1)).toThrowError('You need to provide both key and value for addSessionAttribute method') 61 | expect(() => message.addSessionAttribute('key')).toThrowError('You need to provide both key and value for addSessionAttribute method') 62 | }) 63 | 64 | it('should add a session attribute', () => { 65 | const message = new AlexaMessageBuilder().addSessionAttribute('key', 'value') 66 | expect(message instanceof AlexaMessageBuilder).toBeTruthy() 67 | expect(message.get()).toEqual({ 68 | version: '1.0', 69 | response: { 70 | shouldEndSession: true 71 | }, 72 | sessionAttributes: { 73 | key: 'value' 74 | } 75 | }) 76 | }) 77 | 78 | it('should add multiple session attributes', () => { 79 | const message = new AlexaMessageBuilder() 80 | .addSessionAttribute('key1', 'value1') 81 | .addSessionAttribute('key2', 'value2') 82 | 83 | expect(message instanceof AlexaMessageBuilder).toBeTruthy() 84 | expect(message.get()).toEqual({ 85 | version: '1.0', 86 | response: { 87 | shouldEndSession: true 88 | }, 89 | sessionAttributes: { 90 | key1: 'value1', 91 | key2: 'value2' 92 | } 93 | }) 94 | }) 95 | 96 | it('should keep only the session attribute if they have the same key', () => { 97 | const message = new AlexaMessageBuilder() 98 | .addSessionAttribute('key', 'value1') 99 | .addSessionAttribute('key', 'value2') 100 | 101 | expect(message instanceof AlexaMessageBuilder).toBeTruthy() 102 | expect(message.get()).toEqual({ 103 | version: '1.0', 104 | response: { 105 | shouldEndSession: true 106 | }, 107 | sessionAttributes: { 108 | key: 'value2' 109 | } 110 | }) 111 | }) 112 | }) 113 | 114 | describe('addOutputSpeech', () => { 115 | it('should be a function', () => { 116 | const message = new AlexaMessageBuilder() 117 | expect(typeof message.addOutputSpeech).toBe('function') 118 | }) 119 | 120 | it('should throw an error if type is not PlainText, or SSML or value is not provided', () => { 121 | const message = new AlexaMessageBuilder() 122 | expect(() => message.addOutputSpeech()).toThrowError('You need to provide type and it can be either "PlainText" or "SSML" for addOutputSpeech method') 123 | expect(() => message.addOutputSpeech('test')).toThrowError('You need to provide type and it can be either "PlainText" or "SSML" for addOutputSpeech method') 124 | expect(() => message.addOutputSpeech(5)).toThrowError('You need to provide type and it can be either "PlainText" or "SSML" for addOutputSpeech method') 125 | }) 126 | 127 | it('should throw an error if text is not stringr or value is not provided', () => { 128 | const message = new AlexaMessageBuilder() 129 | expect(() => message.addOutputSpeech('SSML')).toThrowError('You need to provide text as a string for addText, addSSML and addOutputSpeech methods') 130 | expect(() => message.addOutputSpeech('SSML', 5)).toThrowError('You need to provide text as a string for addText, addSSML and addOutputSpeech methods') 131 | }) 132 | 133 | it('should throw an error if you call addText or addSSML more then once', () => { 134 | const message = new AlexaMessageBuilder() 135 | expect(() => message.addOutputSpeech('SSML', 'text').addOutputSpeech('SSML', 'text')).toThrowError('You can call addText or addSSML only once') 136 | }) 137 | 138 | it('with reprompt', () => { 139 | const message = new AlexaMessageBuilder().addOutputSpeech('SSML', 'some text', true) 140 | expect(message.get()).toEqual({ 141 | version: '1.0', 142 | response: { 143 | shouldEndSession: true, 144 | reprompt: { 145 | outputSpeech : { 146 | type: 'SSML', 147 | ssml: 'some text' 148 | } 149 | } 150 | } 151 | }) 152 | }) 153 | 154 | it('without reprompt', () => { 155 | const message = new AlexaMessageBuilder().addOutputSpeech('SSML', 'some text', false) 156 | expect(message.get()).toEqual({ 157 | version: '1.0', 158 | response: { 159 | shouldEndSession: true, 160 | outputSpeech : { 161 | type: 'SSML', 162 | ssml: 'some text' 163 | } 164 | } 165 | }) 166 | }) 167 | 168 | it('before reprompt', () => { 169 | const message = new AlexaMessageBuilder().addOutputSpeech('SSML', 'some text', false).addRepromptSSML('some reprompt text') 170 | expect(message.get()).toEqual({ 171 | version: '1.0', 172 | response: { 173 | shouldEndSession: true, 174 | outputSpeech : { 175 | type: 'SSML', 176 | ssml: 'some text' 177 | }, 178 | reprompt: { 179 | outputSpeech : { 180 | type: 'SSML', 181 | ssml: 'some reprompt text' 182 | } 183 | } 184 | } 185 | }) 186 | }) 187 | 188 | }) 189 | 190 | describe('addSimpleCard', () => { 191 | it('should be a function', () => { 192 | const message = new AlexaMessageBuilder() 193 | expect(typeof message.addSimpleCard).toBe('function') 194 | }) 195 | 196 | it('should return error if title or content are not provided or if they are not a string', () => { 197 | const message = new AlexaMessageBuilder() 198 | expect(() => message.addSimpleCard()).toThrowError('You need to provide title and content as strings for addSimpleCard method') 199 | expect(() => message.addSimpleCard(5)).toThrowError('You need to provide title and content as strings for addSimpleCard method') 200 | expect(() => message.addSimpleCard(5, [])).toThrowError('You need to provide title and content as strings for addSimpleCard method') 201 | expect(() => message.addSimpleCard({}, 6)).toThrowError('You need to provide title and content as strings for addSimpleCard method') 202 | }) 203 | 204 | it('should add simple card', () => { 205 | const message = new AlexaMessageBuilder().addSimpleCard('some title', 'some content') 206 | expect(message.get()).toEqual({ 207 | version: '1.0', 208 | response: { 209 | shouldEndSession: true, 210 | card: { 211 | type: 'Simple', 212 | title: 'some title', 213 | content: 'some content' 214 | } 215 | } 216 | }) 217 | }) 218 | }) 219 | 220 | describe('addStandardCard', () => { 221 | it('should be a function', () => { 222 | const message = new AlexaMessageBuilder() 223 | expect(typeof message.addStandardCard).toBe('function') 224 | }) 225 | 226 | it('should add standard card', () => { 227 | const message = new AlexaMessageBuilder().addStandardCard('some title', 'some text', { smallImageUrl: 'http://example.com/small-image-url.png', largeImageUrl: 'http://example.com/large-image-url.png' }) 228 | expect(message.get()).toEqual({ 229 | version: '1.0', 230 | response: { 231 | shouldEndSession: true, 232 | card: { 233 | type: 'Standard', 234 | title: 'some title', 235 | text: 'some text', 236 | image: {smallImageUrl: 'http://example.com/small-image-url.png', largeImageUrl: 'http://example.com/large-image-url.png'} 237 | } 238 | } 239 | }) 240 | }) 241 | }) 242 | 243 | describe('dialog directives', () => { 244 | const intent = { 245 | name: 'TestIntent', 246 | confirmationStatus: 'None', 247 | slots: { } 248 | } 249 | 250 | describe('addDialogDelegate', () => { 251 | it('should be a function', () => { 252 | const message = new AlexaMessageBuilder() 253 | expect(typeof message.addDialogDelegate).toBe('function') 254 | }) 255 | 256 | it('should add a delegate directive', () => { 257 | const message = new AlexaMessageBuilder().addDialogDelegate(intent) 258 | expect(message.get()).toEqual({ 259 | version: '1.0', 260 | response: { 261 | shouldEndSession: true, 262 | directives: [ 263 | { 264 | type: 'Dialog.Delegate', 265 | updatedIntent: intent 266 | } 267 | ] 268 | } 269 | }) 270 | }) 271 | }) 272 | 273 | describe('addDialogElicitSlot', () => { 274 | it('should be a function', () => { 275 | const message = new AlexaMessageBuilder() 276 | expect(typeof message.addDialogElicitSlot).toBe('function') 277 | }) 278 | 279 | it('should add a elicit slot directive', () => { 280 | const message = new AlexaMessageBuilder().addDialogElicitSlot('slotname', intent) 281 | expect(message.get()).toEqual({ 282 | version: '1.0', 283 | response: { 284 | shouldEndSession: true, 285 | directives: [ 286 | { 287 | type: 'Dialog.ElicitSlot', 288 | slotToElicit: 'slotname', 289 | updatedIntent: intent 290 | } 291 | ] 292 | } 293 | }) 294 | }) 295 | }) 296 | 297 | describe('addDialogConfirmSlot', () => { 298 | it('should be a function', () => { 299 | const message = new AlexaMessageBuilder() 300 | expect(typeof message.addDialogConfirmSlot).toBe('function') 301 | }) 302 | 303 | it('should add a confirm slot directive', () => { 304 | const message = new AlexaMessageBuilder().addDialogConfirmSlot('slotname', intent) 305 | expect(message.get()).toEqual({ 306 | version: '1.0', 307 | response: { 308 | shouldEndSession: true, 309 | directives: [ 310 | { 311 | type: 'Dialog.ConfirmSlot', 312 | slotToConfirm: 'slotname', 313 | updatedIntent: intent 314 | } 315 | ] 316 | } 317 | }) 318 | }) 319 | }) 320 | 321 | describe('addDialogConfirmIntent', () => { 322 | it('should be a function', () => { 323 | const message = new AlexaMessageBuilder() 324 | expect(typeof message.addDialogConfirmIntent).toBe('function') 325 | }) 326 | 327 | it('should add a delegate directive', () => { 328 | const message = new AlexaMessageBuilder().addDialogConfirmIntent(intent) 329 | expect(message.get()).toEqual({ 330 | version: '1.0', 331 | response: { 332 | shouldEndSession: true, 333 | directives: [ 334 | { 335 | type: 'Dialog.ConfirmIntent', 336 | updatedIntent: intent 337 | } 338 | ] 339 | } 340 | }) 341 | }) 342 | }) 343 | }) 344 | }) 345 | -------------------------------------------------------------------------------- /spec/support/jasmine-runner.js: -------------------------------------------------------------------------------- 1 | /*global jasmine, require, process*/ 2 | var Jasmine = require('jasmine'), 3 | SpecReporter = require('jasmine-spec-reporter').SpecReporter, 4 | noop = function () {}, 5 | jrunner = new Jasmine(), 6 | filter 7 | process.argv.slice(2).forEach(option => { 8 | 'use strict' 9 | if (option === 'full') { 10 | jrunner.configureDefaultReporter({ print: noop }) // remove default reporter logs 11 | jasmine.getEnv().addReporter(new SpecReporter()) // add jasmine-spec-reporter 12 | } 13 | if (option.match('^filter=')) { 14 | filter = option.match('^filter=(.*)')[1] 15 | } 16 | }) 17 | jrunner.loadConfigFile() // load jasmine.json configuration 18 | jrunner.execute(undefined, filter) 19 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------