diff --git a/packages/js/assets/user-feedback-form.html b/packages/js/assets/user-feedback-form.html index fe4aa243b..74d344774 100644 --- a/packages/js/assets/user-feedback-form.html +++ b/packages/js/assets/user-feedback-form.html @@ -132,7 +132,30 @@

diff --git a/packages/js/examples/chrome-extension/options.html b/packages/js/examples/chrome-extension/options.html index a8bf63eb3..dbdd50a95 100644 --- a/packages/js/examples/chrome-extension/options.html +++ b/packages/js/examples/chrome-extension/options.html @@ -2,7 +2,7 @@ - + @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/packages/js/examples/chrome-extension/popup.html b/packages/js/examples/chrome-extension/popup.html index efd02063d..12f667035 100644 --- a/packages/js/examples/chrome-extension/popup.html +++ b/packages/js/examples/chrome-extension/popup.html @@ -2,11 +2,11 @@ - + - \ No newline at end of file + diff --git a/packages/js/examples/chrome-extension/setup.sh b/packages/js/examples/chrome-extension/setup.sh index 395288297..2c6efb4db 100644 --- a/packages/js/examples/chrome-extension/setup.sh +++ b/packages/js/examples/chrome-extension/setup.sh @@ -1,3 +1,3 @@ #!/bin/bash -curl -o vendor/honeybadger.min.js https://js.honeybadger.io/v3.2/honeybadger.min.js +curl -o vendor/honeybadger.min.js https://js.honeybadger.io/v6.9/honeybadger.ext.min.js diff --git a/packages/js/package-lock.json b/packages/js/package-lock.json index 03e84c01b..35221b0ba 100644 --- a/packages/js/package-lock.json +++ b/packages/js/package-lock.json @@ -28,6 +28,7 @@ "jest-fetch-mock": "^3.0.3", "nock": "^13.2.1", "rollup": "^2.77.0", + "rollup-plugin-strip-code": "^0.2.7", "rollup-plugin-terser": "^7.0.2", "sinon": "^14.0.0", "supertest": "^6.1.6", @@ -5444,6 +5445,25 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-strip-code": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/rollup-plugin-strip-code/-/rollup-plugin-strip-code-0.2.7.tgz", + "integrity": "sha512-+5t9u/VrHPSfiRWWKMVin+KOtFwFak337FAZxeTjxYDjB3DDoHBQRkXHQvBn713eAfW81t41mGuysqsMXiuTjw==", + "dev": true, + "dependencies": { + "magic-string": "0.25.3", + "rollup-pluginutils": "2.8.1" + } + }, + "node_modules/rollup-plugin-strip-code/node_modules/magic-string": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", + "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, "node_modules/rollup-plugin-terser": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", @@ -5474,6 +5494,21 @@ "node": ">= 10.13.0" } }, + "node_modules/rollup-pluginutils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", + "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10855,6 +10890,27 @@ "fsevents": "~2.3.2" } }, + "rollup-plugin-strip-code": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/rollup-plugin-strip-code/-/rollup-plugin-strip-code-0.2.7.tgz", + "integrity": "sha512-+5t9u/VrHPSfiRWWKMVin+KOtFwFak337FAZxeTjxYDjB3DDoHBQRkXHQvBn713eAfW81t41mGuysqsMXiuTjw==", + "dev": true, + "requires": { + "magic-string": "0.25.3", + "rollup-pluginutils": "2.8.1" + }, + "dependencies": { + "magic-string": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", + "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + } + } + }, "rollup-plugin-terser": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", @@ -10880,6 +10936,23 @@ } } }, + "rollup-pluginutils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", + "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/packages/js/package.json b/packages/js/package.json index 2db87a1e9..6c9f604b7 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -69,6 +69,7 @@ "jest-fetch-mock": "^3.0.3", "nock": "^13.2.1", "rollup": "^2.77.0", + "rollup-plugin-strip-code": "^0.2.7", "rollup-plugin-terser": "^7.0.2", "sinon": "^14.0.0", "supertest": "^6.1.6", diff --git a/packages/js/rollup.config.js b/packages/js/rollup.config.js index 2876cce3f..7eceeaff5 100644 --- a/packages/js/rollup.config.js +++ b/packages/js/rollup.config.js @@ -2,6 +2,7 @@ import replace from '@rollup/plugin-replace' import resolve from '@rollup/plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import { terser } from 'rollup-plugin-terser' +import stripCode from 'rollup-plugin-strip-code' import pkg from './package.json' const sharedPlugins = [ @@ -43,6 +44,25 @@ export default [ plugins: [...sharedPlugins, terser()] }, + // Chrome Extension build (minified) - https://github.com/honeybadger-io/honeybadger-js/issues/1383 + { + input: 'build/src/browser.js', + output: { + name: 'Honeybadger', + file: 'dist/browser/honeybadger.ext.min.js', + format: 'umd', + sourcemap: true + }, + plugins: [ + stripCode({ + start_comment: 'ROLLUP_STRIP_CODE_CHROME_EXTENSION_START', + end_comment: 'ROLLUP_STRIP_CODE_CHROME_EXTENSION_END' + }), + ...sharedPlugins, + terser() + ] + }, + // Server build { input: 'build/src/server.js', diff --git a/packages/js/scripts/release-cdn.sh b/packages/js/scripts/release-cdn.sh index e9f0fa2fd..74c91fe1d 100755 --- a/packages/js/scripts/release-cdn.sh +++ b/packages/js/scripts/release-cdn.sh @@ -34,6 +34,8 @@ aws s3 sync dist/browser/ s3://$HONEYBADGER_JS_S3_BUCKET/$PREFIX \ --include 'honeybadger.js.map'\ --include 'honeybadger.min.js' \ --include 'honeybadger.min.js.map' \ + --include 'honeybadger.ext.min.js' \ + --include 'honeybadger.ext.min.js.map' \ --include 'honeybadger-feedback-form.js' aws cloudfront create-invalidation --distribution-id $HONEYBADGER_DISTRIBUTION_ID --paths "/$PREFIX/*" diff --git a/packages/js/src/browser.ts b/packages/js/src/browser.ts index 1bcfb6582..76ca4f60a 100644 --- a/packages/js/src/browser.ts +++ b/packages/js/src/browser.ts @@ -6,6 +6,7 @@ import breadcrumbs from './browser/integrations/breadcrumbs' import timers from './browser/integrations/timers' import eventListeners from './browser/integrations/event_listeners' import { BrowserTransport } from './browser/transport' +import { BrowserFeedbackForm } from './browser/feedback-form'; const { merge, filter, objectIsExtensible, globalThisOrWindow } = Util @@ -20,6 +21,7 @@ const getProjectRoot = () => { return projectRoot } + export const getUserFeedbackScriptUrl = (version: string) => { const majorMinorVersion = version.split('.').slice(0,2).join('.') return `https://js.honeybadger.io/v${majorMinorVersion}/honeybadger-feedback-form.js` @@ -99,58 +101,8 @@ class Honeybadger extends Client { } public async showUserFeedbackForm(options: Types.UserFeedbackFormOptions = {}) { - if (!this.config || !this.config.apiKey) { - this.logger.debug('Client not initialized') - return - } - - if (!this.__lastNoticeId) { - this.logger.debug("Can't show user feedback form without a notice already reported") - return - } - - const global = globalThisOrWindow() - if (typeof global.document === 'undefined') { - this.logger.debug('global.document is undefined. Cannot attach script') - return - } - - if (this.isUserFeedbackScriptUrlAlreadyVisible()) { - this.logger.debug('User feedback form is already visible') - return - } - - global['honeybadgerUserFeedbackOptions'] = { - ...options, - apiKey: this.config.apiKey, - endpoint: this.config.userFeedbackEndpoint, - noticeId: this.__lastNoticeId - } - - this.appendUserFeedbackScriptTag(global, options) - } - - private appendUserFeedbackScriptTag(window: typeof globalThis, options: Types.UserFeedbackFormOptions = {}) { - const script = window.document.createElement('script') - script.setAttribute('src', this.getUserFeedbackSubmitUrl()) - script.setAttribute('async', 'true') - if (options.onLoad) { - script.onload = options.onLoad - } - (global.document.head || global.document.body).appendChild(script) - } - - private isUserFeedbackScriptUrlAlreadyVisible() { - const global = globalThisOrWindow() - const feedbackScriptUrl =this.getUserFeedbackSubmitUrl() - for (let i = 0; i < global.document.scripts.length; i++) { - const script = global.document.scripts[i] - if (script.src === feedbackScriptUrl) { - return true - } - } - - return false + const form = new BrowserFeedbackForm(this.config, this.logger, this.getUserFeedbackSubmitUrl()); + form.show(this.__lastNoticeId, options); } private getUserFeedbackSubmitUrl() { diff --git a/packages/js/src/browser/feedback-form.ts b/packages/js/src/browser/feedback-form.ts new file mode 100644 index 000000000..834765d8a --- /dev/null +++ b/packages/js/src/browser/feedback-form.ts @@ -0,0 +1,73 @@ +import { Types, Util } from '@honeybadger-io/core' +const { globalThisOrWindow } = Util + +export class BrowserFeedbackForm { + + private readonly config: Types.BrowserConfig + private readonly logger: Types.Logger + private readonly scriptUrl: string + + constructor(config: Types.BrowserConfig, logger: Types.Logger, scriptUrl: string) { + this.config = config + this.logger = logger + this.scriptUrl = scriptUrl + } + + /* ROLLUP_STRIP_CODE_CHROME_EXTENSION_START */ + public show(lastNoticeId: string, options: Types.UserFeedbackFormOptions = {}) { + if (!this.config || !this.config.apiKey) { + this.logger.debug('Client not initialized') + return + } + + if (!lastNoticeId) { + this.logger.debug("Can't show user feedback form without a notice already reported") + return + } + + const global = globalThisOrWindow() + if (typeof global.document === 'undefined') { + this.logger.debug('global.document is undefined. Cannot attach script') + return + } + + if (this.isUserFeedbackScriptUrlAlreadyVisible()) { + this.logger.debug('User feedback form is already visible') + return + } + + global['honeybadgerUserFeedbackOptions'] = { + ...options, + apiKey: this.config.apiKey, + endpoint: this.config.userFeedbackEndpoint, + noticeId: lastNoticeId, + } + + this.appendUserFeedbackScriptTag(global, options) + + } + + private appendUserFeedbackScriptTag(window: typeof globalThis, options: Types.UserFeedbackFormOptions = {}) { + const script = window.document.createElement('script') + script.setAttribute('src', this.scriptUrl) + script.setAttribute('async', 'true') + if (options.onLoad) { + script.onload = options.onLoad + } + (global.document.head || global.document.body).appendChild(script) + } + + private isUserFeedbackScriptUrlAlreadyVisible() { + const global = globalThisOrWindow() + const feedbackScriptUrl = this.scriptUrl + for (let i = 0; i < global.document.scripts.length; i++) { + const script = global.document.scripts[i] + if (script.src === feedbackScriptUrl) { + return true + } + } + + return false + } + /* ROLLUP_STRIP_CODE_CHROME_EXTENSION_END */ +} diff --git a/packages/js/test/e2e/integration.spec.ts b/packages/js/test/e2e/integration.spec.ts index 8a886c5ea..d3b940866 100644 --- a/packages/js/test/e2e/integration.spec.ts +++ b/packages/js/test/e2e/integration.spec.ts @@ -1,5 +1,4 @@ import { test, expect, Page, TestInfo } from '@playwright/test' -import { resolve } from 'path' import type Honeybadger from '../../honeybadger' import type { NoticeTransportPayload } from '../../../core/src/types' @@ -329,10 +328,6 @@ test.describe('Browser Integration', () => { test('it shows user feedback form', async ({ page }) => { const handle = await page.evaluateHandle(_ => { - // @ts-ignore private access - window.Honeybadger.isUserFeedbackScriptUrlAlreadyVisible = () => false - // @ts-ignore private access - window.Honeybadger.appendUserFeedbackScriptTag = () => { } window.Honeybadger.afterNotify(() => { window.Honeybadger.showUserFeedbackForm() }) @@ -340,10 +335,6 @@ test.describe('Browser Integration', () => { }) await handle.dispose() - const relativePath = '../../dist/browser/honeybadger-feedback-form.js' - await page.addScriptTag({ - path: resolve(__dirname, relativePath) - }) await page.waitForSelector('div#honeybadger-feedback') const { notices } = await page.evaluate('results') @@ -358,10 +349,6 @@ test.describe('Browser Integration', () => { test('it shows user feedback form with custom labels', async ({ page }) => { const handle = await page.evaluateHandle(_ => { - // @ts-ignore private access - window.Honeybadger.isUserFeedbackScriptUrlAlreadyVisible = () => false - // @ts-ignore private access - window.Honeybadger.appendUserFeedbackScriptTag = () => { } window.Honeybadger.afterNotify(() => { window.Honeybadger.showUserFeedbackForm({ messages: { @@ -373,10 +360,6 @@ test.describe('Browser Integration', () => { }) await handle.dispose() - const relativePath = '../../dist/browser/honeybadger-feedback-form.js' - await page.addScriptTag({ - path: resolve(__dirname, relativePath) - }) await page.waitForSelector('div#honeybadger-feedback') const { notices } = await page.evaluate('results') @@ -391,10 +374,6 @@ test.describe('Browser Integration', () => { test('it sends user feedback for notice on submit', async ({ page }) => { const handle = await page.evaluateHandle(_ => { - // @ts-ignore private access - window.Honeybadger.isUserFeedbackScriptUrlAlreadyVisible = () => false - // @ts-ignore private access - window.Honeybadger.appendUserFeedbackScriptTag = () => { } window.Honeybadger.afterNotify(() => { window.Honeybadger.showUserFeedbackForm() }) @@ -402,10 +381,6 @@ test.describe('Browser Integration', () => { }) await handle.dispose() - const relativePath = '../../dist/browser/honeybadger-feedback-form.js' - await page.addScriptTag({ - path: resolve(__dirname, relativePath) - }) await page.waitForSelector('div#honeybadger-feedback') const { notices } = await page.evaluate('results') @@ -417,9 +392,9 @@ test.describe('Browser Integration', () => { const name = 'integration test' const email = 'integration-test@honeybadger.io' const comment = 'ci integration comment' - await page.type('#honeybadger-feedback-name', name) - await page.type('#honeybadger-feedback-email', email) - await page.type('#honeybadger-feedback-comment', comment) + await page.fill('#honeybadger-feedback-name', name) + await page.fill('#honeybadger-feedback-email', email) + await page.fill('#honeybadger-feedback-comment', comment) await page.click('#honeybadger-feedback-submit') const feedbackSubmitUrl = 'https://api.honeybadger.io/v2/feedback' + @@ -437,10 +412,6 @@ test.describe('Browser Integration', () => { test('it closes user feedback form on cancel', async ({ page }) => { const handle = await page.evaluateHandle(_ => { - // @ts-ignore private access - window.Honeybadger.isUserFeedbackScriptUrlAlreadyVisible = () => false - // @ts-ignore private access - window.Honeybadger.appendUserFeedbackScriptTag = () => { } window.Honeybadger.afterNotify(() => { window.Honeybadger.showUserFeedbackForm() }) @@ -448,10 +419,6 @@ test.describe('Browser Integration', () => { }) await handle.dispose() - const relativePath = '../../dist/browser/honeybadger-feedback-form.js' - await page.addScriptTag({ - path: resolve(__dirname, relativePath) - }) await page.waitForSelector('div#honeybadger-feedback') const { notices } = await page.evaluate('results') diff --git a/packages/js/test/e2e/sandbox.html b/packages/js/test/e2e/sandbox.html index 30e93c336..4019b304f 100644 --- a/packages/js/test/e2e/sandbox.html +++ b/packages/js/test/e2e/sandbox.html @@ -34,6 +34,8 @@ debug: true }) + Honeybadger.getUserFeedbackSubmitUrl = () => 'dist/browser/honeybadger-feedback-form.js' + Honeybadger.__transport.send = function (options, payload) { results.notices.push(payload); return Promise.resolve({ statusCode: 201, body: JSON.stringify({id: 'test'}) }) diff --git a/packages/js/test/unit/browser.test.ts b/packages/js/test/unit/browser.test.ts index 4efec51a4..49f9fc4eb 100644 --- a/packages/js/test/unit/browser.test.ts +++ b/packages/js/test/unit/browser.test.ts @@ -1,4 +1,4 @@ -import Singleton, { getUserFeedbackScriptUrl } from '../../src/browser' +import Singleton from '../../src/browser' import { nullLogger } from './helpers' import fetch from 'jest-fetch-mock' @@ -167,125 +167,4 @@ describe('browser client', function () { expect(payload.notifier.name).toEqual('@honeybadger-io/js') }) }) - - describe('showUserFeedbackForm', function () { - beforeEach(function () { - window['honeybadgerUserFeedbackOptions'] = undefined - - for (let i = window.document.scripts.length - 1; i >= 0; i--) { - if (window.document.scripts[i].src.indexOf('https://js.honeybadger.io') > -1) { - window.document.scripts[i].parentNode.removeChild(window.document.scripts[i]) - } - } - }) - - it('should do nothing if client is not properly initialized', function () { - client.configure({ - apiKey: undefined - }) - client.showUserFeedbackForm() - expect(window['honeybadgerUserFeedbackOptions']).toBeUndefined() - }) - - it('should remember id of last reported notice', function () { - client.configure({ - apiKey: 'testing' - }) - const id1 = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' - const id2 = '48b98609-dd3b-48ee-bffc-d51f309a2dfb' - fetch.mockResponses( - [JSON.stringify({ id: id1 }), { status: 201 }], - [JSON.stringify({ id: id2 }), { status: 201 }] - ) - - return new Promise((resolve) => { - client.afterNotify(function (err, notice) { - expect(err).toBeUndefined() - - if (notice.message === 'testing') { - expect(notice.id).toBe(id1) - // @ts-expect-error __lastNoticeId is private - expect(client.__lastNoticeId).toBe(id1) - } - - if (notice.message === 'testing 2') { - expect(notice.id).toBe(id2) - // @ts-expect-error __lastNoticeId is private - expect(client.__lastNoticeId).not.toBe(id1) - // @ts-expect-error __lastNoticeId is private - expect(client.__lastNoticeId).toBe(id2) - - resolve() - } - }) - - client.notify('testing') - client.notify('testing 2') - }) - }) - - it('should do nothing if no notice has been reported yet', function () { - client.configure({ - apiKey: 'testing' - }) - client.showUserFeedbackForm() - expect(window['honeybadgerUserFeedbackOptions']).toBeUndefined() - - }) - - it('should build a feedback script url only with major.minor version', function () { - const version = '4.8.1' - const url = getUserFeedbackScriptUrl(version) - expect(url).toMatch('/v4.8/') - }) - - it('should add user feedback script tag on document.head', function () { - const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' - client.configure({ - apiKey: 'testing' - }) - // @ts-expect-error - client.__lastNoticeId = id - client.showUserFeedbackForm() - expect(window['honeybadgerUserFeedbackOptions']).toMatchObject({ - noticeId: id - }) - expect(window.document.head.innerHTML).toMatch(``) - }) - - it('should add user feedback options in window object', function () { - const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' - client.configure({ - apiKey: 'testing' - }) - // @ts-expect-error - client.__lastNoticeId = id - const options = { - labels: { name: 'Your Name ???' }, - buttons: { cancel: 'Stop!' }, - messages: { thanks: 'Your feedback is greatly appreciated!' } - } - client.showUserFeedbackForm(options) - expect(window['honeybadgerUserFeedbackOptions']).toEqual({ - apiKey: client.config.apiKey, - endpoint: client.config.userFeedbackEndpoint, - noticeId: id, - ...options - }) - }) - - it('should not load feedback script more than once', function () { - const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' - client.configure({ - apiKey: 'testing' - }) - // @ts-expect-error - client.__lastNoticeId = id - const scriptsCount = window.document.scripts.length - client.showUserFeedbackForm() - expect(window.document.scripts.length).toEqual(scriptsCount + 1) - client.showUserFeedbackForm() - expect(window.document.scripts.length).toEqual(scriptsCount + 1) - }) - }) }) diff --git a/packages/js/test/unit/browser/feedback-form.browser.test.ts b/packages/js/test/unit/browser/feedback-form.browser.test.ts new file mode 100644 index 000000000..2e7ad2e20 --- /dev/null +++ b/packages/js/test/unit/browser/feedback-form.browser.test.ts @@ -0,0 +1,133 @@ +import fetch from 'jest-fetch-mock'; +import Singleton, { getUserFeedbackScriptUrl } from '../../../src/browser'; +import { nullLogger } from '../helpers'; + +describe('showUserFeedbackForm', function () { + + let client: typeof Singleton + + beforeEach(function () { + client = Singleton.factory({ + logger: nullLogger() + }) + + fetch.resetMocks() + + window['honeybadgerUserFeedbackOptions'] = undefined + + for (let i = window.document.scripts.length - 1; i >= 0; i--) { + if (window.document.scripts[i].src.indexOf('https://js.honeybadger.io') > -1) { + window.document.scripts[i].parentNode.removeChild(window.document.scripts[i]) + } + } + }) + + it('should do nothing if client is not properly initialized', function () { + client.configure({ + apiKey: undefined + }) + client.showUserFeedbackForm() + expect(window['honeybadgerUserFeedbackOptions']).toBeUndefined() + }) + + it('should remember id of last reported notice', function () { + client.configure({ + apiKey: 'testing' + }) + const id1 = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' + const id2 = '48b98609-dd3b-48ee-bffc-d51f309a2dfb' + fetch.mockResponses( + [JSON.stringify({ id: id1 }), { status: 201 }], + [JSON.stringify({ id: id2 }), { status: 201 }] + ) + + return new Promise((resolve) => { + client.afterNotify(function (err, notice) { + expect(err).toBeUndefined() + + if (notice.message === 'testing') { + expect(notice.id).toBe(id1) + // @ts-expect-error __lastNoticeId is private + expect(client.__lastNoticeId).toBe(id1) + } + + if (notice.message === 'testing 2') { + expect(notice.id).toBe(id2) + // @ts-expect-error __lastNoticeId is private + expect(client.__lastNoticeId).not.toBe(id1) + // @ts-expect-error __lastNoticeId is private + expect(client.__lastNoticeId).toBe(id2) + + resolve() + } + }) + + client.notify('testing') + client.notify('testing 2') + }) + }) + + it('should do nothing if no notice has been reported yet', function () { + client.configure({ + apiKey: 'testing' + }) + client.showUserFeedbackForm() + expect(window['honeybadgerUserFeedbackOptions']).toBeUndefined() + + }) + + it('should build a feedback script url only with major.minor version', function () { + const version = '4.8.1' + const url = getUserFeedbackScriptUrl(version) + expect(url).toMatch('/v4.8/') + }) + + it('should add user feedback script tag on document.head', function () { + const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' + client.configure({ + apiKey: 'testing' + }) + // @ts-expect-error + client.__lastNoticeId = id + client.showUserFeedbackForm() + expect(window['honeybadgerUserFeedbackOptions']).toMatchObject({ + noticeId: id + }) + expect(window.document.head.innerHTML).toMatch(``) + }) + + it('should add user feedback options in window object', function () { + const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' + client.configure({ + apiKey: 'testing' + }) + // @ts-expect-error + client.__lastNoticeId = id + const options = { + labels: { name: 'Your Name ???' }, + buttons: { cancel: 'Stop!' }, + messages: { thanks: 'Your feedback is greatly appreciated!' } + } + client.showUserFeedbackForm(options) + expect(window['honeybadgerUserFeedbackOptions']).toEqual({ + apiKey: client.config.apiKey, + endpoint: client.config.userFeedbackEndpoint, + noticeId: id, + ...options + }) + }) + + it('should not load feedback script more than once', function () { + const id = '48b98609-dd3b-48ee-bffc-d51f309a2dfa' + client.configure({ + apiKey: 'testing' + }) + // @ts-expect-error + client.__lastNoticeId = id + const scriptsCount = window.document.scripts.length + client.showUserFeedbackForm() + expect(window.document.scripts.length).toEqual(scriptsCount + 1) + client.showUserFeedbackForm() + expect(window.document.scripts.length).toEqual(scriptsCount + 1) + }) +})