├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .mjmlconfig ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── components └── MjQrCode.js ├── examples └── index.mjml ├── gulpfile.babel.js ├── lib └── MjQrCode.js ├── package.json ├── test └── mjml-qr-code.test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | # Indentation style 8 | # Possible values - tab, space 9 | indent_style = space 10 | 11 | # Indentation size in single-spaced characters 12 | # Possible values - an integer, tab 13 | indent_size = 2 14 | 15 | # Line ending file format 16 | # Possible values - lf, crlf, cr 17 | end_of_line = lf 18 | 19 | # File character encoding 20 | # Possible values - latin1, utf-8, utf-16be, utf-16le 21 | charset = utf-8 22 | 23 | # Denotes whether to trim whitespace at the end of lines 24 | # Possible values - true, false 25 | trim_trailing_whitespace = true 26 | 27 | # Denotes whether file should end with a newline 28 | # Possible values - true, false 29 | insert_final_newline = true 30 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint-config-airbnb-base", 4 | "eslint-config-airbnb-base/rules/strict" 5 | ], 6 | "parser": "@babel/eslint-parser", 7 | "rules": { 8 | 9 | "padded-blocks": 0, 10 | "space-before-function-paren": 0, 11 | "semi": 0, 12 | 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | examples/**/*.html 2 | venv 3 | 4 | ### Vim ### 5 | [._]*.s[a-w][a-z] 6 | [._]s[a-w][a-z] 7 | *.un~ 8 | Session.vim 9 | .netrwhist 10 | *~ 11 | 12 | 13 | ### OSX ### 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | 22 | # Thumbnails 23 | ._* 24 | 25 | # Files that might appear in the root of a volume 26 | .DocumentRevisions-V100 27 | .fseventsd 28 | .Spotlight-V100 29 | .TemporaryItems 30 | .Trashes 31 | .VolumeIcon.icns 32 | 33 | # Directories potentially created on remote AFP share 34 | .AppleDB 35 | .AppleDesktop 36 | Network Trash Folder 37 | Temporary Items 38 | .apdisk 39 | 40 | 41 | ### Python ### 42 | # Byte-compiled / optimized / DLL files 43 | __pycache__/ 44 | *.py[cod] 45 | *$py.class 46 | 47 | # C extensions 48 | *.so 49 | 50 | # Distribution / packaging 51 | .Python 52 | env/ 53 | build/ 54 | develop-eggs/ 55 | dist/ 56 | downloads/ 57 | eggs/ 58 | .eggs/ 59 | parts/ 60 | sdist/ 61 | var/ 62 | *.egg-info/ 63 | .installed.cfg 64 | *.egg 65 | 66 | # PyInstaller 67 | # Usually these files are written by a python script from a template 68 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 69 | *.manifest 70 | *.spec 71 | 72 | # Installer logs 73 | pip-log.txt 74 | pip-delete-this-directory.txt 75 | 76 | # Unit test / coverage reports 77 | htmlcov/ 78 | .tox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *,cover 85 | 86 | # Translations 87 | *.mo 88 | *.pot 89 | 90 | # Django stuff: 91 | *.log 92 | 93 | # Sphinx documentation 94 | docs/_build/ 95 | 96 | # PyBuilder 97 | target/ 98 | 99 | 100 | ### Node ### 101 | # Logs 102 | logs 103 | *.log 104 | npm-debug.log* 105 | 106 | # Runtime data 107 | pids 108 | *.pid 109 | *.seed 110 | 111 | # Directory for instrumented libs generated by jscoverage/JSCover 112 | lib-cov 113 | 114 | # Coverage directory used by tools like istanbul 115 | coverage 116 | 117 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 118 | .grunt 119 | 120 | # node-waf configuration 121 | .lock-wscript 122 | 123 | # Compiled binary addons (http://nodejs.org/api/addons.html) 124 | build/Release 125 | 126 | # Dependency directory 127 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 128 | node_modules 129 | 130 | -------------------------------------------------------------------------------- /.mjmlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "./lib/MjQrCode.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 16 4 | cache: yarn 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 QuickChart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mjml-qr-code 2 | [![npm](https://img.shields.io/npm/v/mjml-qr-code)](https://www.npmjs.com/package/mjml-qr-code) 3 | [![Build Status](https://travis-ci.com/typpo/mjml-qr-code.svg?branch=master)](https://travis-ci.com/typpo/mjml-qr-code) 4 | 5 | A component for adding QR codes to your email using an open-source [QuickChart](https://quickchart.io) provider. 6 | 7 | ## Usage 8 | 9 | This mjml... 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | Will show this QR code: 24 | 25 | ![QR code](https://quickchart.io/qr?text=hello%20world) 26 | 27 | Customize the color, size, positioning, and other QR parameters using the attributes below. 28 | 29 | ## Setup 30 | 31 | Install via npm: 32 | 33 | ``` 34 | npm install mjml-qr-code --save 35 | ``` 36 | 37 | Then add the package to your `.mjmlconfig`: 38 | 39 | ``` 40 | { 41 | "packages": [ 42 | "mjml-qr-code/lib/MjQrCode.js" 43 | ] 44 | } 45 | ``` 46 | 47 | ## Attributes 48 | 49 | The `` tag supports all the attributes of the `` tag. View those attributes [here](https://mjml.io/documentation/#mjml-image). 50 | 51 | In addition to regular image attributes which you can using for sizing and positioning, the component supports the following QR-specific attributes: 52 | 53 | | Name | Description | Required? | Default | | 54 | |------------------------|-----------------------------------------------------------|-----------|----------------|---| 55 | | value | The content encoded in your QR code | Yes | | | 56 | | color | The color of the QR code | No | 000000 (black) | | 57 | | background-color | The background of the QR code | No | ffffff (white) | | 58 | | qr-margin | The number of QR blocks to leave empty around the QR code | No | 4 | | 59 | | error-correction-level | The QR [error correction level](https://en.wikipedia.org/wiki/QR_code#Error_correction) | No | M | | 60 | | width | Width of the QR code image | No | 200 | | 61 | | host | The host of the QR image server | No | quickchart.io | | 62 | | protocol | The protocol of the QR image server | No | https | | 63 | 64 | ## Hosting 65 | 66 | By default, this component uses the public [QuickChart](https://quickchart.io) web service to render QR codes, but you can use the `host` attribute to point to your own QR renderer. 67 | -------------------------------------------------------------------------------- /components/MjQrCode.js: -------------------------------------------------------------------------------- 1 | import { registerDependencies } from 'mjml-validator' 2 | import { BodyComponent } from 'mjml-core' 3 | 4 | registerDependencies({ 5 | 'mj-image-text': [], 6 | 'mj-body': ['mj-qr-code'], 7 | 'mj-section': ['mj-qr-code'], 8 | 'mj-wrapper': ['mj-qr-code'], 9 | 'mj-column': ['mj-qr-code'], 10 | }) 11 | 12 | export default class MjQrCode extends BodyComponent { 13 | static endingTag = true 14 | 15 | static allowedAttributes = { 16 | // QR-related attributes 17 | value: 'string', 18 | color: 'color', 19 | 'background-color': 'color', 20 | 'qr-margin': 'integer', 21 | 'error-correction-level': 'string', 22 | width: 'unit(px)', 23 | host: 'string', 24 | protocol: 'string', 25 | 26 | // Image attributes 27 | alt: 'string', 28 | href: 'string', 29 | name: 'string', 30 | src: 'string', 31 | srcset: 'string', 32 | title: 'string', 33 | rel: 'string', 34 | align: 'enum(left,center,right)', 35 | border: 'string', 36 | 'border-bottom': 'string', 37 | 'border-left': 'string', 38 | 'border-right': 'string', 39 | 'border-top': 'string', 40 | 'border-radius': 'unit(px,%){1,4}', 41 | 'container-background-color': 'color', 42 | 'fluid-on-mobile': 'boolean', 43 | padding: 'unit(px,%){1,4}', 44 | 'padding-bottom': 'unit(px,%)', 45 | 'padding-left': 'unit(px,%)', 46 | 'padding-right': 'unit(px,%)', 47 | 'padding-top': 'unit(px,%)', 48 | target: 'string', 49 | height: 'unit(px,auto)', 50 | 'max-height': 'unit(px,%)', 51 | 'font-size': 'unit(px)', 52 | usemap: 'string', 53 | } 54 | 55 | static defaultAttributes = { 56 | // QR-related attributes 57 | value: null, 58 | color: '#000000', 59 | 'background-color': '#ffffff', 60 | 'qr-margin': 4, 61 | 'error-correction-level': 'M', 62 | width: 200, 63 | host: 'quickchart.io', 64 | protocol: 'https', 65 | } 66 | 67 | getUrl() { 68 | const val = this.getAttribute('value') 69 | if (!val) { 70 | throw new Error('You must specify a "value" attribute for mjml-qr-code') 71 | } 72 | const content = encodeURIComponent(val) 73 | const width = this.getAttribute('width') 74 | const foregroundColor = encodeURIComponent(this.getAttribute('color').replace('#', '')) 75 | const backgroundColor = encodeURIComponent( 76 | this.getAttribute('background-color').replace('#', ''), 77 | ) 78 | const ecLevel = this.getAttribute('error-correction-level') 79 | const margin = this.getAttribute('qr-margin') 80 | return `${this.getAttribute('protocol')}://${this.getAttribute( 81 | 'host', 82 | )}/qr?text=${content}&size=${width}&dark=${foregroundColor}&light=${backgroundColor}&ecLevel=${ecLevel}&margin=${margin}&ref=mjml` 83 | } 84 | 85 | renderImage() { 86 | const attributes = {} 87 | Object.keys(MjQrCode.allowedAttributes).forEach((key) => { 88 | attributes[key] = this.getAttribute(key) 89 | }) 90 | attributes.src = this.getUrl() 91 | return ` 92 | 95 | 96 | ` 97 | } 98 | 99 | render() { 100 | return this.renderMJML(this.renderImage()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/index.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Use the QR code below as your event ticket. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import babel from 'gulp-babel' 3 | import watch from 'gulp-watch' 4 | import log from 'fancy-log' 5 | import fs from 'fs' 6 | import path from 'path' 7 | import mjml2html from 'mjml' 8 | import { registerComponent } from 'mjml-core' 9 | 10 | const walkSync = (dir, filelist = []) => { 11 | fs.readdirSync(dir).forEach(file => { 12 | filelist = fs.statSync(path.join(dir, file)).isDirectory() 13 | ? walkSync(path.join(dir, file), filelist) 14 | : filelist.concat(path.join(dir, file)) 15 | }) 16 | return filelist 17 | } 18 | 19 | const watchedComponents = walkSync('./components') 20 | 21 | const compile = () => { 22 | return gulp 23 | .src(path.normalize('components/**/*.js')) 24 | .pipe(babel()) 25 | .on('error', log) 26 | .pipe(gulp.dest('lib')) 27 | .on('end', () => { 28 | watchedComponents.forEach(compPath => { 29 | if (compPath.endsWith('.swp')) { 30 | return; 31 | } 32 | const fullPath = path.join(process.cwd(), compPath.replace(/^components/, 'lib')) 33 | delete require.cache[fullPath] 34 | registerComponent(require(fullPath).default) 35 | }) 36 | 37 | fs.readFile(path.normalize('./examples/index.mjml'), 'utf8', (err, data) => { 38 | if (err) throw err 39 | const result = mjml2html(data) 40 | fs.writeFileSync(path.normalize('./examples/index.html'), result.html) 41 | }) 42 | }) 43 | } 44 | 45 | gulp.task('build', compile) 46 | 47 | gulp.task('watch', () => { 48 | compile() 49 | return watch([path.normalize('components/**/*.js'), path.normalize('index.mjml')], compile) 50 | }) 51 | -------------------------------------------------------------------------------- /lib/MjQrCode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _mjmlValidator = require("mjml-validator"); 11 | 12 | var _mjmlCore = require("mjml-core"); 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 19 | 20 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 21 | 22 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 23 | 24 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 25 | 26 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 27 | 28 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 29 | 30 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 31 | 32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 33 | 34 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 35 | 36 | (0, _mjmlValidator.registerDependencies)({ 37 | 'mj-image-text': [], 38 | 'mj-body': ['mj-qr-code'], 39 | 'mj-section': ['mj-qr-code'], 40 | 'mj-wrapper': ['mj-qr-code'], 41 | 'mj-column': ['mj-qr-code'] 42 | }); 43 | 44 | var MjQrCode = /*#__PURE__*/function (_BodyComponent) { 45 | _inherits(MjQrCode, _BodyComponent); 46 | 47 | var _super = _createSuper(MjQrCode); 48 | 49 | function MjQrCode() { 50 | _classCallCheck(this, MjQrCode); 51 | 52 | return _super.apply(this, arguments); 53 | } 54 | 55 | _createClass(MjQrCode, [{ 56 | key: "getUrl", 57 | value: function getUrl() { 58 | var val = this.getAttribute('value'); 59 | 60 | if (!val) { 61 | throw new Error('You must specify a "value" attribute for mjml-qr-code'); 62 | } 63 | 64 | var content = encodeURIComponent(val); 65 | var width = this.getAttribute('width'); 66 | var foregroundColor = encodeURIComponent(this.getAttribute('color').replace('#', '')); 67 | var backgroundColor = encodeURIComponent(this.getAttribute('background-color').replace('#', '')); 68 | var ecLevel = this.getAttribute('error-correction-level'); 69 | var margin = this.getAttribute('qr-margin'); 70 | return "".concat(this.getAttribute('protocol'), "://").concat(this.getAttribute('host'), "/qr?text=").concat(content, "&size=").concat(width, "&dark=").concat(foregroundColor, "&light=").concat(backgroundColor, "&ecLevel=").concat(ecLevel, "&margin=").concat(margin, "&ref=mjml"); 71 | } 72 | }, { 73 | key: "renderImage", 74 | value: function renderImage() { 75 | var _this = this; 76 | 77 | var attributes = {}; 78 | Object.keys(MjQrCode.allowedAttributes).forEach(function (key) { 79 | attributes[key] = _this.getAttribute(key); 80 | }); 81 | attributes.src = this.getUrl(); 82 | return "\n \n \n "); 83 | } 84 | }, { 85 | key: "render", 86 | value: function render() { 87 | return this.renderMJML(this.renderImage()); 88 | } 89 | }]); 90 | 91 | return MjQrCode; 92 | }(_mjmlCore.BodyComponent); 93 | 94 | exports["default"] = MjQrCode; 95 | 96 | _defineProperty(MjQrCode, "endingTag", true); 97 | 98 | _defineProperty(MjQrCode, "allowedAttributes", { 99 | // QR-related attributes 100 | value: 'string', 101 | color: 'color', 102 | 'background-color': 'color', 103 | 'qr-margin': 'integer', 104 | 'error-correction-level': 'string', 105 | width: 'unit(px)', 106 | host: 'string', 107 | protocol: 'string', 108 | // Image attributes 109 | alt: 'string', 110 | href: 'string', 111 | name: 'string', 112 | src: 'string', 113 | srcset: 'string', 114 | title: 'string', 115 | rel: 'string', 116 | align: 'enum(left,center,right)', 117 | border: 'string', 118 | 'border-bottom': 'string', 119 | 'border-left': 'string', 120 | 'border-right': 'string', 121 | 'border-top': 'string', 122 | 'border-radius': 'unit(px,%){1,4}', 123 | 'container-background-color': 'color', 124 | 'fluid-on-mobile': 'boolean', 125 | padding: 'unit(px,%){1,4}', 126 | 'padding-bottom': 'unit(px,%)', 127 | 'padding-left': 'unit(px,%)', 128 | 'padding-right': 'unit(px,%)', 129 | 'padding-top': 'unit(px,%)', 130 | target: 'string', 131 | height: 'unit(px,auto)', 132 | 'max-height': 'unit(px,%)', 133 | 'font-size': 'unit(px)', 134 | usemap: 'string' 135 | }); 136 | 137 | _defineProperty(MjQrCode, "defaultAttributes", { 138 | // QR-related attributes 139 | value: null, 140 | color: '#000000', 141 | 'background-color': '#ffffff', 142 | 'qr-margin': 4, 143 | 'error-correction-level': 'M', 144 | width: 200, 145 | host: 'quickchart.io', 146 | protocol: 'https' 147 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mjml-qr-code", 3 | "version": "2.0.1", 4 | "description": "Embed QR codes in your emails", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "gulp watch", 8 | "build": "gulp build", 9 | "lint": "eslint components", 10 | "format": "prettier --write --single-quote --trailing-comma all '{components,test}/**/*.js'", 11 | "test": "jest" 12 | }, 13 | "author": "Ian Webster (quickchart.io)", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/typpo/mjml-qr-code.git" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.18.5", 20 | "@babel/eslint-parser": "^7.18.2", 21 | "@babel/plugin-proposal-class-properties": "^7.17.12", 22 | "@babel/preset-env": "^7.18.2", 23 | "@babel/register": "^7.17.7", 24 | "eslint": "^8.18.0", 25 | "eslint-config-airbnb": "^19.0.4", 26 | "eslint-plugin-import": "^2.26.0", 27 | "eslint-plugin-jsx-a11y": "^6.5.1", 28 | "eslint-plugin-react": "^7.28.0", 29 | "eslint-plugin-react-hooks": "^4.6.0", 30 | "fancy-log": "^1.3.3", 31 | "gulp": "^4.0.2", 32 | "gulp-babel": "^8.0.0", 33 | "gulp-watch": "^5.0.1", 34 | "jest": "^26.0.1", 35 | "prettier": "^2.0.5" 36 | }, 37 | "dependencies": { 38 | "mjml": "^4.13.0", 39 | "mjml-core": "^4.13.0", 40 | "mjml-validator": "^4.13.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/mjml-qr-code.test.js: -------------------------------------------------------------------------------- 1 | import mjml2html from 'mjml' 2 | import { registerComponent } from 'mjml-core' 3 | 4 | import MjQrCode from '../components/MjQrCode' 5 | 6 | function toHtml(mjml) { 7 | const conversion = mjml2html(mjml) 8 | const errors = conversion.errors 9 | if (errors.length > 0) { 10 | return errors 11 | } 12 | return conversion.html 13 | } 14 | 15 | describe('mjml-qr-code', () => { 16 | beforeAll(() => { 17 | registerComponent(MjQrCode) 18 | }) 19 | 20 | it('should render the qr code', () => { 21 | expect( 22 | toHtml(` 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | `), 33 | ).toContain(' 44 | 45 | 46 | 47 | 48 | `), 49 | ).toContain('&ecLevel=Q') 50 | }) 51 | 52 | it('should support custom colors', () => { 53 | expect( 54 | toHtml(` 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | `), 65 | ).toContain('dark=ff0000&light=ffffff') 66 | }) 67 | 68 | it('should support custom margin', () => { 69 | expect( 70 | toHtml(` 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | `), 81 | ).toContain('&margin=5') 82 | }) 83 | 84 | it('should support custom host and protocol', () => { 85 | expect( 86 | toHtml(` 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | `), 97 | ).toContain(' 108 | 109 | 110 | 111 | 112 | `) 113 | }).toThrow() 114 | }) 115 | }) 116 | --------------------------------------------------------------------------------