├── .babelrc
├── .gitignore
├── .npmrc
├── .travis.yml
├── LICENSE
├── NOTICE
├── README.md
├── RELEASING.md
├── examples
├── .nojekyll
└── index.html
├── package.json
├── postcss.config.js
├── rollup.config.js
├── src
├── bundle.js
├── index.js
├── microfeedback-button.css
└── send-json.js
└── test
├── helpers
└── setup-browser-env.js
└── test-microfeedback-button.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false
7 | }
8 | ]
9 | ],
10 | "plugins": [
11 | "external-helpers",
12 | "transform-object-assign"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | *.log
3 | # Built files
4 | es/*
5 | dist/*
6 | lib/*
7 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | sudo: false
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Steven Loria and Lauren Barker
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | CSS for the feedback widget was adapted from ga-feedback.
2 |
3 | ga-feedback License
4 | ===================
5 |
6 | Copyright © 2015-2016 Xavi Esteve (http://xaviesteve.com)
7 |
8 | Permission is hereby granted, free of charge, to any person
9 | obtaining a copy of this software and associated documentation
10 | files (the “Software”), to deal in the Software without
11 | restriction, including without limitation the rights to use,
12 | copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the
14 | Software is furnished to do so, subject to the following
15 | conditions:
16 |
17 | The above copyright notice and this permission notice shall be
18 | included in all copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 | OTHER DEALINGS IN THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # microfeedback-button
2 |
3 | [](https://www.npmjs.org/package/microfeedback-button)
4 | [](https://travis-ci.org/microfeedback/microfeedback-button)
5 |
6 | A simple widget for capturing user feedback. Use together with a microfeedback backend such as [microfeedback-github](https://github.com/microfeedback/microfeedback-github).
7 |
8 | Uses [sweetalert2](https://sweetalert2.github.io/) under the hood to
9 | display responsive, customizable, and accessible input dialogs.
10 |
11 | ## Documentation (with demos)
12 |
13 | https://microfeedback.js.org/ui-components/microfeedback-button/
14 |
15 | ## API
16 |
17 | ### `microfeedback([elem], [options])`
18 |
19 | - `elem`: The `HTMLElement` to bind to. If not given, the default button
20 | will be rendered.
21 | - `options`
22 | - `url`: URL for your microfeedback backend. If `null`,
23 | feedback will be logged to the console. May also be a function that
24 | receives `btn` and `result` (the user input) as arguments and returns a URL. Default: `null`
25 | - `buttonText`: Text to display in the default button. Default: `'Feedback'`
26 | - `buttonAriaLabel`: `aria-label` for the default button. Default: `'Send feedback'`
27 | - `title`: Title to display in the dialog. Default: `'Send feedback'`
28 | - `placeholder`: Placeholder text in the dialog input. Default: `'Describe your issue or share your ideas'`
29 | - `backgroundColor`: Background color for the default button. Default: `'#3085d6'`
30 | - `color`: Color for the default button text. Default: `'#fff'`
31 | - `animation`: Enable animations. Default: `true`
32 | - `showDialog`: Function that displays a sweetalert2 dialog. Returns a
33 | `Promise` that resolves to the input result. Use `return btn.alert(...)` to
34 | display the dialog.
35 | - `getPayload`: Function that receives `btn` (the
36 | `MicroFeedbackButton` instance) and input result and returns
37 | the request payload to send to the microfeedback backend.
38 | - `preSend`: Function that receives `btn` (the
39 | `MicroFeedbackButton` instance) and input result. This is called
40 | before sending the request to the microfeedback backend. Useful for
41 | displaying a "Thank you" message with `return btn.alert(...)`.
42 | - `optimistic`: If `true`, display success message immediately after
43 | user submits input (don't wait for request to finish). If `false`,
44 | wait until request finishes to show message (use together with
45 | `onSuccess` to customize message). Default: `true`
46 | - `showSuccessDialog`: Function that receives `btn` (the
47 | `MicroFeedbackButton` instance) and input result and
48 | displays a dialog using `return btn.alert(...)`.
49 | - `onSuccess`: Function called when request succeeds. Receives `btn` (the
50 | `MicroFeedbackButton` instance) and input result. By default,
51 | calls `options.showSuccessDialog(btn, input)` if `optimistic` is
52 | `false`, otherwise noop.
53 | - `onFailure`: Function called when request fails. Receives `btn` (the
54 | `MicroFeedbackButton` instance). Default: `noop`
55 |
56 | Additionally, any valid [sweetalert2](https://sweetalert2.github.io/#configuration) option may be
57 | passed to configure the input dialog.
58 |
59 |
60 | ### Methods
61 |
62 | #### `btn.alert(...args)`
63 |
64 | Display a sweetalert2 dialog. This is equivalent to the `swal` function
65 | from sweetalert2.
66 |
67 | ## Developing
68 |
69 | * `npm install`
70 | * To run tests: `npm test`
71 | * To run tests in watch mode: `npm test -- --watch`
72 | * To run the example: `npm run dev`
73 |
74 | ## License
75 |
76 | MIT Licensed.
77 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | ```
4 | npm run release
5 | ```
6 |
--------------------------------------------------------------------------------
/examples/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microfeedback/microfeedback-button/430228740cfd2b0b0366b831a055748e39637840/examples/.nojekyll
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | microfeedback-button Demo
7 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
microfeedback-button
44 |
microfeedback-button is a small library for adding a feedback button to your site.
45 |
It uses sweetalert2 for displaying beautiful, responsive input dialogs.
46 |
47 | Use this library together with a microfeedback backend, such as
48 | microfeedback-github ,
49 | which will record user feedback as GitHub issues.
50 |
51 |
52 |
Default button
53 |
The default button with no configuration is at the bottom right of the page.
54 |
55 |
Binding to a specific element
56 |
You can bind the feedback dialog to any element.
57 |
58 |
Preview
59 |
60 |
Success message after response
61 |
62 | By default, the "thank you" message will be shown as soon as the user submits their
63 | input. Set optimistic: false
if you want to show the message
64 | only after the request to the backend is complete.
65 |
66 |
67 |
Preview
68 |
69 |
Handling failure
70 |
71 | Use onFailure()
to handle failed requests when optimistic
72 | is false
.
73 |
74 |
75 |
Preview
76 |
77 |
Custom success dialog
78 |
Use the showSuccessDialog()
hook to customize the success dialog.
79 |
Preview
80 |
81 |
showSuccessDialog()
can be used together with optimistic: false
.
82 |
Preview
83 |
84 |
85 |
Advanced: Multiple inputs
86 |
Here is an example using sweetalert2's preConfirm() function to support multiple inputs.
87 | See the sweetalert2 docs for more information.
88 |
89 |
Preview
90 |
91 |
Documentation
92 |
93 | View the project's documentation on GitHub: https://microfeedback.js.org/ui-components/microfeedback-button
94 |
95 |
96 |
97 |
98 |
99 |
208 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "microfeedback-button",
3 | "version": "1.1.0",
4 | "description": "A simple feedback button/widget. Use together with a microfeedback backend.",
5 | "browser": "dist/microfeedback-button.js",
6 | "main": "lib/microfeedback-button.js",
7 | "module": "es/microfeedback-button.js",
8 | "scripts": {
9 | "dev": "NODE_ENV=development SERVE=true rollup -c --watch -o dist/microfeedback-button.js",
10 | "build:commonjs": "NODE_ENV=cjs rollup -c -o lib/microfeedback-button.js",
11 | "build:es": "NODE_ENV=es rollup -c -o es/microfeedback-button.js",
12 | "build:umd": "NODE_ENV=development rollup -c -o dist/microfeedback-button.js",
13 | "build:umd:min": "NODE_ENV=production rollup -c -o dist/microfeedback-button.min.js",
14 | "build": "npm-run-all --parallel build:commonjs build:es build:umd build:umd:min",
15 | "clean": "rimraf lib dist es",
16 | "prebuild": "npm run clean",
17 | "prepare": "npm run build",
18 | "pretest": "npm run build:umd",
19 | "lint": "xo src/* && stylelint src/*.css",
20 | "test": "npm run lint && NODE_ENV=test ava test --serial",
21 | "test:debug": "NODE_ENV=test iron-node ./node_modules/ava/profile.js test/tests.js",
22 | "release": "np"
23 | },
24 | "files": [
25 | "es",
26 | "lib",
27 | "dist",
28 | "src"
29 | ],
30 | "repository": "microfeedback/microfeedback-button",
31 | "authors": [
32 | "Steven Loria (https://github.com/sloria)",
33 | "Lauren Barker (https://github.com/laurenbarker)"
34 | ],
35 | "license": "MIT",
36 | "keywords": [
37 | "user",
38 | "customer",
39 | "feedback",
40 | "ui",
41 | "widget",
42 | "button",
43 | "microfeedback"
44 | ],
45 | "devDependencies": {
46 | "autoprefixer": "^9.0.0",
47 | "ava": "^3.0.0",
48 | "babel-cli": "^6.24.1",
49 | "babel-plugin-external-helpers": "^6.22.0",
50 | "babel-plugin-transform-object-assign": "^6.22.0",
51 | "babel-preset-env": "^1.6.1",
52 | "browser-env": "^3.2.0",
53 | "cssnano": "^4.0.0",
54 | "np": "^6.0.0",
55 | "npm-run-all": "^4.0.2",
56 | "rimraf": "^3.0.0",
57 | "rollup": "^1.29.1",
58 | "rollup-plugin-babel": "^3.0.3",
59 | "rollup-plugin-filesize": "^6.2.1",
60 | "rollup-plugin-json": "^4.0.0",
61 | "rollup-plugin-node-resolve": "^5.2.0",
62 | "rollup-plugin-postcss": "^2.0.4",
63 | "rollup-plugin-serve": "^1.0.1",
64 | "rollup-plugin-uglify": "^6.0.0",
65 | "rollup-watch": "^4.3.1",
66 | "sinon": "^9.0.0",
67 | "stylelint": "^13.0.0",
68 | "stylelint-config-standard": "^20.0.0",
69 | "syn": "^0.14.1",
70 | "xo": "^0.23.0"
71 | },
72 | "dependencies": {
73 | "sweetalert2": "^7.22.2"
74 | },
75 | "ava": {
76 | "require": [
77 | "./test/helpers/setup-browser-env.js"
78 | ]
79 | },
80 | "xo": {
81 | "envs": [
82 | "node",
83 | "browser"
84 | ],
85 | "space": true,
86 | "rules": {
87 | "capitalized-comments": 0,
88 | "operator-linebreak": 0,
89 | "import/no-unassigned-import": 0,
90 | "no-warning-comments": 0,
91 | "comma-dangle": [
92 | "error",
93 | "always-multiline"
94 | ]
95 | }
96 | },
97 | "stylelint": {
98 | "extends": "stylelint-config-standard",
99 | "rules": {
100 | "declaration-colon-newline-after": null
101 | }
102 | },
103 | "browserslist": [
104 | "IE >= 11"
105 | ],
106 | "prettier": {
107 | "bracketSpacing": false,
108 | "useTabs": false,
109 | "singleQuote": true,
110 | "trailingComma": "all"
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const cssnano = require('cssnano');
2 |
3 | const PROD = process.env.NODE_ENV === 'production';
4 |
5 | module.exports = {
6 | plugins: [
7 | require('autoprefixer'),
8 | PROD && cssnano(),
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import {resolve} from 'path';
2 |
3 | import nodeResolve from 'rollup-plugin-node-resolve';
4 | import json from 'rollup-plugin-json';
5 | import serve from 'rollup-plugin-serve';
6 | import {uglify} from 'rollup-plugin-uglify';
7 | import babel from 'rollup-plugin-babel';
8 | import postcss from 'rollup-plugin-postcss';
9 | import filesize from 'rollup-plugin-filesize';
10 |
11 | const env = process.env.NODE_ENV;
12 |
13 | const config = {
14 | plugins: [],
15 | };
16 |
17 | if (env === 'cjs' || env === 'es') {
18 | config.input = resolve('src', 'index.js');
19 | config.output = {format: env};
20 | config.external = ['sweetalert2'];
21 | config.plugins.push(postcss(), babel());
22 | }
23 |
24 | if (env === 'development' || env === 'production') {
25 | config.input = resolve('src', 'bundle.js');
26 | config.output = {
27 | name: 'microfeedback',
28 | format: 'umd',
29 | globals: {sweetalert2: 'swal'},
30 | };
31 | config.plugins.push(
32 | postcss(),
33 | nodeResolve({
34 | jsnext: true,
35 | }),
36 | babel({
37 | exclude: ['**/*.json'],
38 | }),
39 | json()
40 | );
41 | }
42 |
43 | if (env === 'production') {
44 | config.plugins.push(uglify());
45 | }
46 |
47 | if (process.env.SERVE === 'true') {
48 | config.plugins.push(serve({contentBase: ['dist', 'examples'], open: true}));
49 | }
50 |
51 | config.plugins.push(filesize());
52 | export default config;
53 |
--------------------------------------------------------------------------------
/src/bundle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Entry point for UMD builds that bundles
3 | * sweetalert2's CSS.
4 | */
5 | import microfeedback from '.';
6 | import 'sweetalert2/dist/sweetalert2.css';
7 |
8 | export default microfeedback;
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import swal from 'sweetalert2';
2 | import './microfeedback-button.css';
3 | import sendJSON from './send-json';
4 |
5 | // Less typing
6 | const d = document;
7 | const noop = () => {};
8 | const clickedClass = 'microfeedback-button--clicked';
9 |
10 | const makeButton = options =>
11 | `${
14 | options.buttonText
15 | } `;
16 | const defaults = {
17 | url: null,
18 | buttonText: 'Feedback',
19 | buttonAriaLabel: 'Send feedback',
20 | title: 'Send feedback',
21 | placeholder: 'Describe your issue or share your ideas',
22 | extra: null,
23 | backgroundColor: '#3085d6',
24 | color: '#fff',
25 | optimistic: true,
26 | showDialog: btn => {
27 | const swalOpts = {
28 | title: btn.options.title,
29 | input: 'textarea',
30 | inputPlaceholder: btn.options.placeholder,
31 | showCancelButton: true,
32 | confirmButtonText: 'Send',
33 | };
34 | if (!btn.options.optimistic) {
35 | swalOpts.showLoaderOnConfirm = true;
36 | swalOpts.preConfirm = value => btn.onSubmit({value});
37 | swalOpts.allowOutsideClick = () => !swal.isLoading();
38 | }
39 | // Allow passing any valid sweetalert2 options
40 | Object.keys(btn.options).forEach(each => {
41 | if (swal.isValidParameter(each)) {
42 | swalOpts[each] = btn.options[each];
43 | }
44 | });
45 | return btn.alert(swalOpts);
46 | },
47 | showSuccessDialog: btn => {
48 | return btn.alert(
49 | 'Thank you!',
50 | 'Your feedback has been submitted.',
51 | 'success'
52 | );
53 | },
54 | getPayload: (btn, {value: body}) => {
55 | const payload = {body};
56 | if (btn.options.extra) {
57 | payload.extra = btn.options.extra;
58 | }
59 | return payload;
60 | },
61 | preSend: (btn, input) => {
62 | if (btn.options.optimistic) {
63 | // Show thank you message before request is sent so the
64 | // user doesn't have to wait
65 | return btn.options.showSuccessDialog(btn, input);
66 | }
67 | },
68 | sendRequest: (btn, url, payload) => {
69 | return sendJSON({
70 | url,
71 | method: 'POST',
72 | payload,
73 | });
74 | },
75 | onSuccess: (btn, input, response) => {
76 | if (!btn.options.optimistic) {
77 | return btn.options.showSuccessDialog(btn, input, response);
78 | }
79 | },
80 | onFailure: noop,
81 | };
82 |
83 | class MicroFeedbackButton {
84 | constructor(element, options) {
85 | const opts = element instanceof HTMLElement ? options : element;
86 | this.options = Object.assign({}, defaults, opts);
87 | if (!this.options.url) {
88 | console.warn(
89 | 'options.url not provided. Feedback will only be logged to the console.'
90 | );
91 | }
92 |
93 | this.appended = false;
94 | this._parent = null;
95 | if (element instanceof HTMLElement) {
96 | this.el = element;
97 | } else {
98 | // assume element is an object
99 | const buttonParent = d.createElement('div');
100 | buttonParent.innerHTML = makeButton(this.options);
101 | d.body.appendChild(buttonParent);
102 | this._parent = buttonParent;
103 | this.appended = true;
104 | this.el = buttonParent.querySelector('.microfeedback-button');
105 | }
106 | this.el.addEventListener('click', this.onClick.bind(this), false);
107 | }
108 |
109 | alert(...args) {
110 | return swal(...args);
111 | }
112 |
113 | onSubmit(input) {
114 | // Backend requires body in payload
115 | if (input.dismiss) {
116 | return null;
117 | }
118 | const payload = this.options.getPayload(this, input);
119 | // microfeedback backends requires 'body'
120 | if (payload.body) {
121 | this.options.preSend(this, input);
122 | let promise;
123 | const url =
124 | typeof this.options.url === 'function'
125 | ? this.options.url(this, input)
126 | : this.options.url;
127 | if (url) {
128 | promise = this.options.sendRequest(this, url, payload, input);
129 | } else {
130 | console.debug('microfeedback payload:');
131 | console.debug(payload);
132 | promise = Promise.resolve(payload);
133 | }
134 | return promise.then(
135 | this.options.onSuccess.bind(this, this, input),
136 | this.options.onFailure.bind(this, this, input)
137 | );
138 | }
139 | }
140 |
141 | onClick(e) {
142 | // eslint-disable-next-line no-unused-expressions
143 | e && e.preventDefault();
144 | this.el.classList.add(clickedClass);
145 | const promise = this.options.showDialog(this).then(input => {
146 | this.el.classList.remove(clickedClass);
147 | return input;
148 | });
149 | if (this.options.optimistic) {
150 | promise.then(this.onSubmit.bind(this));
151 | }
152 | return promise;
153 | }
154 |
155 | destroy() {
156 | this.el.removeEventListener('click', this.onClick.bind(this));
157 | if (this.appended) {
158 | d.body.removeChild(this._parent);
159 | }
160 | }
161 | }
162 |
163 | const factory = (element, options) => new MicroFeedbackButton(element, options);
164 | factory.MicroFeedbackButton = MicroFeedbackButton;
165 | export default factory;
166 |
--------------------------------------------------------------------------------
/src/microfeedback-button.css:
--------------------------------------------------------------------------------
1 | button.microfeedback-button {
2 | cursor: pointer;
3 | text-decoration: none;
4 | position: fixed;
5 | bottom: 0;
6 | right: 3.125em;
7 | padding: 4px 7px;
8 | font-size: 0.875em;
9 | border-radius: 5px 5px 0 0;
10 | z-index: 1001;
11 | transition: all 0.2s ease-in-out;
12 |
13 | /* Button reset */
14 | border: none;
15 | margin: 0;
16 | width: auto;
17 | overflow: visible;
18 | background: transparent;
19 | color: inherit;
20 |
21 | /* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
22 | line-height: normal;
23 |
24 | /* Corrects font smoothing for webkit */
25 | -webkit-font-smoothing: inherit;
26 | -moz-osx-font-smoothing: inherit;
27 |
28 | /* Corrects inability to style clickable `input` types in iOS */
29 | -webkit-appearance: none;
30 | }
31 |
32 | button.microfeedback-button:hover,
33 | button.microfeedback-button--clicked {
34 | /* Darken background color a bit */
35 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNikAQAACIAHF/uBd8AAAAASUVORK5CYII=");
36 | padding-bottom: 10px;
37 | }
38 |
--------------------------------------------------------------------------------
/src/send-json.js:
--------------------------------------------------------------------------------
1 | const defaults = {
2 | method: 'POST',
3 | url: null,
4 | payload: null,
5 | };
6 |
7 | export default options => {
8 | const opts = Object.assign({}, defaults, options);
9 | return new Promise((resolve, reject) => {
10 | const req = new XMLHttpRequest();
11 | req.open(opts.method, opts.url, true);
12 | req.setRequestHeader('Content-Type', 'application/json');
13 | req.setRequestHeader('Accept', 'application/json');
14 | req.send(JSON.stringify(opts.payload));
15 | req.addEventListener('load', () => {
16 | if (req.status < 400) {
17 | const data = JSON.parse(req.response);
18 | resolve(data);
19 | } else {
20 | reject(new Error(req.statusText));
21 | }
22 | });
23 | req.addEventListener('error', () => {
24 | reject(new Error('Network Error'));
25 | });
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/test/helpers/setup-browser-env.js:
--------------------------------------------------------------------------------
1 | const browserEnv = require('browser-env');
2 |
3 | browserEnv();
4 | // Prevents an error when loading sweetalert2's weakmap polyfill
5 | window.WeakMap = () => {};
6 | // Prevents warnings from being logged in tests
7 | window.scrollTo = () => {};
8 |
--------------------------------------------------------------------------------
/test/test-microfeedback-button.js:
--------------------------------------------------------------------------------
1 | import sinon from 'sinon';
2 | import test from 'ava';
3 | import syn from 'syn';
4 | import {MicroFeedbackButton} from '../dist/microfeedback-button';
5 |
6 | const $ = document.querySelector.bind(document);
7 |
8 | test('renders button', t => {
9 | const btn = new MicroFeedbackButton({url: false});
10 | t.truthy($('.microfeedback-button'));
11 | btn.destroy();
12 | });
13 |
14 | test('optimistic mode is on by default', async t => {
15 | const onSuccessSpy = sinon.spy();
16 | const btn = new MicroFeedbackButton({
17 | onSuccess: onSuccessSpy,
18 | // simulate request
19 | url: true,
20 | sendRequest: () => Promise.resolve(),
21 | });
22 | t.true(btn.options.optimistic);
23 | const alertSpy = sinon.spy(btn, 'alert');
24 | await btn.onSubmit({value: 'foo'});
25 | t.true(onSuccessSpy.called);
26 | t.true(alertSpy.called);
27 | t.deepEqual(onSuccessSpy.args[0][0], btn);
28 | t.deepEqual(onSuccessSpy.args[0][1], {value: 'foo'});
29 |
30 | btn.alert.restore();
31 | });
32 |
33 | test('non-optimistic mode', async t => {
34 | const onSuccessSpy = sinon.spy();
35 | const btn = new MicroFeedbackButton({
36 | optimistic: false,
37 | onSuccess: onSuccessSpy,
38 | // simulate request
39 | url: true,
40 | sendRequest: () => Promise.resolve(),
41 | });
42 | const alertSpy = sinon.spy(btn, 'alert');
43 | await btn.onSubmit({value: 'foo'});
44 | t.true(onSuccessSpy.called);
45 | // btn.alert should not be called when optimistic is false
46 | // because we show the thank you message is shown after the promise resolves
47 | t.false(alertSpy.called);
48 | t.deepEqual(onSuccessSpy.args[0][0], btn);
49 | t.deepEqual(onSuccessSpy.args[0][1], {value: 'foo'});
50 |
51 | btn.alert.restore();
52 | });
53 |
54 | test('customizing the button text', t => {
55 | const btn = new MicroFeedbackButton({
56 | url: false,
57 | buttonText: 'BeefDack',
58 | });
59 | t.is(btn.el.innerHTML, 'BeefDack');
60 | });
61 |
62 | test.cb('clicking button shows dialog', t => {
63 | const btn = new MicroFeedbackButton({url: false});
64 | syn.click(btn.el, () => {
65 | const popup = $('.swal2-popup');
66 | t.truthy(popup);
67 | btn.destroy();
68 | t.end();
69 | });
70 | });
71 |
72 | test.cb('can type in dialog and submit', t => {
73 | const spy = sinon.spy();
74 | const btn = new MicroFeedbackButton({url: false, animation: false, preSend: spy});
75 | syn.click(btn.el).delay(() => {
76 | const input = $('.swal2-textarea');
77 | syn.type(input, 'bar baz', () => {
78 | const submit = $('button.swal2-confirm');
79 | syn.click(submit).delay(() => {
80 | t.truthy(spy.called);
81 | t.deepEqual(spy.args[0][1], {value: 'bar baz'});
82 | btn.destroy();
83 | t.end();
84 | });
85 | }, 200);
86 | }, 200);
87 | });
88 |
89 | test.cb('sends request to URL', t => {
90 | const server = sinon.fakeServer.create();
91 | const url = 'http://test.test/';
92 | const btn = new MicroFeedbackButton({url});
93 | const response = {
94 | backend: {name: 'github', version: '1.2.3'},
95 | result: {},
96 | };
97 | server.respondWith('POST', url, [
98 | 201,
99 | {'Content-Type': 'application/json'},
100 | JSON.stringify(response),
101 | ]);
102 | syn.click(btn.el, () => {
103 | const input = $('.swal2-textarea');
104 | syn.type(input, 'foo bar baz', () => {
105 | const submit = $('button.swal2-confirm');
106 | syn.click(submit).delay(() => {
107 | server.respond();
108 | t.is(server.requests.length, 1);
109 | btn.destroy();
110 | t.end();
111 | });
112 | });
113 | });
114 | });
115 |
116 | test.cb('sends request to URL returned by function', t => {
117 | const server = sinon.fakeServer.create();
118 | const url = 'http://foo.test';
119 | const btn = new MicroFeedbackButton({
120 | url(btn, {value}) {
121 | t.true(value.includes('oo'));
122 | return url;
123 | },
124 | });
125 | const response = {
126 | backend: {name: 'github', version: '1.2.3'},
127 | result: {},
128 | };
129 | server.respondWith('POST', url, [
130 | 201,
131 | {'Content-Type': 'application/json'},
132 | JSON.stringify(response),
133 | ]);
134 | syn.click(btn.el, () => {
135 | const input = $('.swal2-textarea');
136 | syn.type(input, 'fool', () => {
137 | const submit = $('button.swal2-confirm');
138 | syn.click(submit).delay(() => {
139 | server.respond();
140 | t.is(server.requests.length, 1);
141 | t.is(server.requests[0].url, url);
142 | btn.destroy();
143 | t.end();
144 | });
145 | });
146 | });
147 | });
148 |
149 | test.cb('sends extra information in request', t => {
150 | const server = sinon.fakeServer.create();
151 | const url = 'http://test.test/';
152 | const btn = new MicroFeedbackButton({url, extra: {foo: 42}});
153 | const response = {
154 | backend: {name: 'github', version: '1.2.3'},
155 | result: {},
156 | };
157 | server.respondWith('POST', url, [
158 | 201,
159 | {'Content-Type': 'application/json'},
160 | JSON.stringify(response),
161 | ]);
162 |
163 | syn.click(btn.el, () => {
164 | const input = $('.swal2-textarea');
165 | syn.type(input, 'foo bar baz', () => {
166 | const submit = $('button.swal2-confirm');
167 | syn.click(submit).delay(() => {
168 | server.respond();
169 | t.is(server.requests.length, 1);
170 | const reqBody = JSON.parse(server.requests[0].requestBody);
171 | t.deepEqual(reqBody.extra, {foo: 42});
172 | btn.destroy();
173 | t.end();
174 | });
175 | });
176 | });
177 | });
178 |
--------------------------------------------------------------------------------