From cbe64d4a814a31d4c9339222db9548a5218d1062 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 6 Nov 2023 19:00:21 -0500 Subject: [PATCH 1/4] feat(feedback): Add `openDialog` and `closeDialog` onto integration interface * Adds `openDialog` and `closeDialog` public methods on the Feedback integration class * Rename `hideDialog` -> `closeDialog` (for Widget) * Rename type `Widget` -> `FeedbackWidget` * Refactor internal logic when creating widgets --- packages/feedback/src/integration.ts | 98 ++++++++++++++----- packages/feedback/src/types/index.ts | 4 +- packages/feedback/src/widget/createWidget.ts | 42 +++++--- .../feedback/test/widget/createWidget.test.ts | 17 +++- 4 files changed, 121 insertions(+), 40 deletions(-) diff --git a/packages/feedback/src/integration.ts b/packages/feedback/src/integration.ts index c1ff31824258..42dccde98137 100644 --- a/packages/feedback/src/integration.ts +++ b/packages/feedback/src/integration.ts @@ -16,7 +16,7 @@ import { SUBMIT_BUTTON_LABEL, SUCCESS_MESSAGE_TEXT, } from './constants'; -import type { FeedbackInternalOptions, OptionalFeedbackConfiguration, Widget } from './types'; +import type { FeedbackInternalOptions, FeedbackWidget, OptionalFeedbackConfiguration } from './types'; import { mergeOptions } from './util/mergeOptions'; import { createActorStyles } from './widget/Actor.css'; import { createShadowHost } from './widget/createShadowHost'; @@ -48,12 +48,12 @@ export class Feedback implements Integration { /** * Reference to widget element that is created when autoInject is true */ - private _widget: Widget | null; + private _widget: FeedbackWidget | null; /** * List of all widgets that are created from the integration */ - private _widgets: Set; + private _widgets: Set; /** * Reference to the host element where widget is inserted @@ -166,15 +166,7 @@ export class Feedback implements Integration { } try { - // TODO: This is only here for hot reloading - if (this._host) { - this.remove(); - } - const existingFeedback = doc.querySelector(`#${this.options.id}`); - if (existingFeedback) { - existingFeedback.remove(); - } - // TODO: End hotloading + this._cleanupWidgetIfExists(); const { autoInject } = this.options; @@ -183,20 +175,49 @@ export class Feedback implements Integration { return; } - this._widget = this._createWidget(this.options); + this._createWidget(this.options); } catch (err) { logger.error(err); } } + /** + * Allows user to open the dialog box. Creates a new widget if + * `autoInject` was false, otherwise re-uses the default widget that was + * created during initialization of the integration. + */ + public openDialog(): void { + if (!this._widget) { + this._createWidget(this.options); + } + + if (!this._widget) { + return; + } + + this._widget.openDialog(); + } + + /** + * Closes the dialog for the default widget, if it exists + */ + public closeDialog(): void { + if (!this._widget) { + // Nothing to do if widget does not exist + return; + } + + this._widget.closeDialog(); + } + /** * Adds click listener to attached element to open a feedback dialog */ - public attachTo(el: Element | string, optionOverrides: OptionalFeedbackConfiguration): Widget | null { + public attachTo(el: Element | string, optionOverrides: OptionalFeedbackConfiguration): FeedbackWidget | null { try { const options = mergeOptions(this.options, optionOverrides); - return this._ensureShadowHost(options, ({ shadow }) => { + return this._ensureShadowHost(options, ({ shadow }) => { const targetEl = typeof el === 'string' ? doc.querySelector(el) : typeof el.addEventListener === 'function' ? el : null; @@ -205,9 +226,7 @@ export class Feedback implements Integration { return null; } - const widget = createWidget({ shadow, options, attachTo: targetEl }); - this._widgets.add(widget); - return widget; + return createWidget({ shadow, options, attachTo: targetEl }); }); } catch (err) { logger.error(err); @@ -218,9 +237,21 @@ export class Feedback implements Integration { /** * Creates a new widget. Accepts partial options to override any options passed to constructor. */ - public createWidget(optionOverrides: OptionalFeedbackConfiguration): Widget | null { + public createWidget( + optionOverrides: OptionalFeedbackConfiguration & { shouldCreateActor?: boolean }, + ): FeedbackWidget | null { try { - return this._createWidget(mergeOptions(this.options, optionOverrides)); + const widget = this._createWidget(mergeOptions(this.options, optionOverrides)); + + if (widget) { + this._widgets.add(widget); + + if (!this._widget) { + this._widget = widget; + } + } + + return widget; } catch (err) { logger.error(err); return null; @@ -230,7 +261,7 @@ export class Feedback implements Integration { /** * Removes a single widget */ - public removeWidget(widget: Widget | null | undefined): boolean { + public removeWidget(widget: FeedbackWidget | null | undefined): boolean { if (!widget) { return false; } @@ -240,6 +271,12 @@ export class Feedback implements Integration { widget.removeActor(); widget.removeDialog(); this._widgets.delete(widget); + + if (this._widget === widget) { + // TODO: is more clean-up needed? e.g. call remove() + this._widget = null; + } + return true; } } catch (err) { @@ -270,11 +307,25 @@ export class Feedback implements Integration { this._hasInsertedActorStyles = false; } + /** + * Clean-up the widget if it already exists in the DOM. This shouldn't happen + * in prod, but can happen in development with hot module reloading. + */ + protected _cleanupWidgetIfExists(): void { + if (this._host) { + this.remove(); + } + const existingFeedback = doc.querySelector(`#${this.options.id}`); + if (existingFeedback) { + existingFeedback.remove(); + } + } + /** * Creates a new widget, after ensuring shadow DOM exists */ - protected _createWidget(options: FeedbackInternalOptions): Widget | null { - return this._ensureShadowHost(options, ({ shadow }) => { + protected _createWidget(options: FeedbackInternalOptions): FeedbackWidget | null { + return this._ensureShadowHost(options, ({ shadow }) => { const widget = createWidget({ shadow, options }); if (!this._hasInsertedActorStyles && widget.actor) { @@ -282,7 +333,6 @@ export class Feedback implements Integration { this._hasInsertedActorStyles = true; } - this._widgets.add(widget); return widget; }); } diff --git a/packages/feedback/src/types/index.ts b/packages/feedback/src/types/index.ts index 9f4d0609c417..f54149404445 100644 --- a/packages/feedback/src/types/index.ts +++ b/packages/feedback/src/types/index.ts @@ -324,7 +324,7 @@ export interface FeedbackComponent { * - dialog + feedback form * - shadow root? */ -export interface Widget { +export interface FeedbackWidget { actor: ActorComponent | undefined; dialog: DialogComponent | undefined; @@ -333,6 +333,6 @@ export interface Widget { removeActor: () => void; openDialog: () => void; - hideDialog: () => void; + closeDialog: () => void; removeDialog: () => void; } diff --git a/packages/feedback/src/widget/createWidget.ts b/packages/feedback/src/widget/createWidget.ts index 688e82bfeae1..480e3d476219 100644 --- a/packages/feedback/src/widget/createWidget.ts +++ b/packages/feedback/src/widget/createWidget.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/core'; import { logger } from '@sentry/utils'; -import type { FeedbackFormData, FeedbackInternalOptions, Widget } from '../types'; +import type { FeedbackFormData, FeedbackInternalOptions, FeedbackWidget } from '../types'; import { handleFeedbackSubmit } from '../util/handleFeedbackSubmit'; import type { ActorComponent } from './Actor'; import { Actor } from './Actor'; @@ -10,15 +10,35 @@ import { Dialog } from './Dialog'; import { SuccessMessage } from './SuccessMessage'; interface CreateWidgetParams { + /** + * Shadow DOM to append to + */ shadow: ShadowRoot; - options: FeedbackInternalOptions; + + /** + * Feedback integration options + */ + options: FeedbackInternalOptions & { shouldCreateActor?: boolean }; + + /** + * An element to attach to, that when clicked, will open a dialog + */ attachTo?: Element; + + /** + * If false, will not create an actor + */ + shouldCreateActor?: boolean; } /** * Creates a new widget. Returns public methods that control widget behavior. */ -export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): Widget { +export function createWidget({ + shadow, + options: { shouldCreateActor = true, ...options }, + attachTo, +}: CreateWidgetParams): FeedbackWidget { let actor: ActorComponent | undefined; let dialog: DialogComponent | undefined; let isDialogOpen: boolean = false; @@ -159,7 +179,7 @@ export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): } }, onCancel: () => { - hideDialog(); + closeDialog(); showActor(); }, onSubmit: _handleFeedbackSubmit, @@ -184,9 +204,9 @@ export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): } /** - * Hides the dialog + * Closes the dialog */ - function hideDialog(): void { + function closeDialog(): void { if (dialog) { dialog.close(); isDialogOpen = false; @@ -202,7 +222,7 @@ export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): */ function removeDialog(): void { if (dialog) { - hideDialog(); + closeDialog(); const dialogEl = dialog.el; dialogEl && dialogEl.remove(); dialog = undefined; @@ -226,11 +246,11 @@ export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): } } - if (!attachTo) { + if (attachTo) { + attachTo.addEventListener('click', handleActorClick); + } else if (shouldCreateActor) { actor = Actor({ buttonLabel: options.buttonLabel, onClick: handleActorClick }); actor.el && shadow.appendChild(actor.el); - } else { - attachTo.addEventListener('click', handleActorClick); } return { @@ -246,7 +266,7 @@ export function createWidget({ shadow, options, attachTo }: CreateWidgetParams): removeActor, openDialog, - hideDialog, + closeDialog, removeDialog, }; } diff --git a/packages/feedback/test/widget/createWidget.test.ts b/packages/feedback/test/widget/createWidget.test.ts index dda36095bf29..1a18b5b93605 100644 --- a/packages/feedback/test/widget/createWidget.test.ts +++ b/packages/feedback/test/widget/createWidget.test.ts @@ -66,7 +66,7 @@ jest.mock('../../src/util/sendFeedbackRequest', () => { }); function createShadowAndWidget( - feedbackOptions?: Partial, + feedbackOptions?: Partial & { shouldCreateActor?: boolean }, createWidgetOptions?: Partial[0]>, ) { const { shadow } = createShadowHost({ @@ -93,9 +93,20 @@ describe('createWidget', () => { }); it('creates widget with actor', () => { - const { widget } = createShadowAndWidget(); + const { shadow, widget } = createShadowAndWidget(); expect(widget.actor?.el).toBeInstanceOf(HTMLButtonElement); - expect(widget.actor?.el?.textContent).toBe(DEFAULT_OPTIONS.buttonLabel); + const actorEl = widget.actor?.el as HTMLButtonElement; + expect(actorEl.textContent).toBe(DEFAULT_OPTIONS.buttonLabel); + // No dialog until actor is clicked + expect(widget.dialog).toBeUndefined(); + expect(shadow.contains(actorEl)).toBe(true); + }); + + it('creates widget without actor', () => { + const { widget } = createShadowAndWidget({ + shouldCreateActor: false, + }); + expect(widget.actor?.el).toBeUndefined(); // No dialog until actor is clicked expect(widget.dialog).toBeUndefined(); }); From 804330b6b71c52bed08a18967e0ef721d03a5966 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 6 Nov 2023 20:23:14 -0500 Subject: [PATCH 2/4] fixes + add tests --- packages/feedback/src/integration.ts | 45 +++-- packages/feedback/test/integration.test.ts | 206 +++++++++++++++++++++ 2 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 packages/feedback/test/integration.test.ts diff --git a/packages/feedback/src/integration.ts b/packages/feedback/src/integration.ts index 42dccde98137..cad6af1e16fc 100644 --- a/packages/feedback/src/integration.ts +++ b/packages/feedback/src/integration.ts @@ -188,7 +188,7 @@ export class Feedback implements Integration { */ public openDialog(): void { if (!this._widget) { - this._createWidget(this.options); + this._createWidget({ ...this.options, shouldCreateActor: false }); } if (!this._widget) { @@ -213,9 +213,9 @@ export class Feedback implements Integration { /** * Adds click listener to attached element to open a feedback dialog */ - public attachTo(el: Element | string, optionOverrides: OptionalFeedbackConfiguration): FeedbackWidget | null { + public attachTo(el: Element | string, optionOverrides?: OptionalFeedbackConfiguration): FeedbackWidget | null { try { - const options = mergeOptions(this.options, optionOverrides); + const options = mergeOptions(this.options, optionOverrides || {}); return this._ensureShadowHost(options, ({ shadow }) => { const targetEl = @@ -226,7 +226,14 @@ export class Feedback implements Integration { return null; } - return createWidget({ shadow, options, attachTo: targetEl }); + const widget = createWidget({ shadow, options, attachTo: targetEl }); + this._widgets.add(widget); + + if (!this._widget) { + this._widget = widget; + } + + return widget; }); } catch (err) { logger.error(err); @@ -238,21 +245,12 @@ export class Feedback implements Integration { * Creates a new widget. Accepts partial options to override any options passed to constructor. */ public createWidget( - optionOverrides: OptionalFeedbackConfiguration & { shouldCreateActor?: boolean }, + optionOverrides?: OptionalFeedbackConfiguration & { shouldCreateActor?: boolean }, ): FeedbackWidget | null { try { - const widget = this._createWidget(mergeOptions(this.options, optionOverrides)); - - if (widget) { - this._widgets.add(widget); - - if (!this._widget) { - this._widget = widget; - } - } - - return widget; + return this._createWidget(mergeOptions(this.options, optionOverrides || {})); } catch (err) { + console.error(err); logger.error(err); return null; } @@ -286,6 +284,13 @@ export class Feedback implements Integration { return false; } + /** + * Returns the default (first-created) widget + */ + public getWidget(): FeedbackWidget | null { + return this._widget; + } + /** * Removes the Feedback integration (including host, shadow DOM, and all widgets) */ @@ -324,7 +329,7 @@ export class Feedback implements Integration { /** * Creates a new widget, after ensuring shadow DOM exists */ - protected _createWidget(options: FeedbackInternalOptions): FeedbackWidget | null { + protected _createWidget(options: FeedbackInternalOptions & { shouldCreateActor?: boolean }): FeedbackWidget | null { return this._ensureShadowHost(options, ({ shadow }) => { const widget = createWidget({ shadow, options }); @@ -333,6 +338,12 @@ export class Feedback implements Integration { this._hasInsertedActorStyles = true; } + this._widgets.add(widget); + + if (!this._widget) { + this._widget = widget; + } + return widget; }); } diff --git a/packages/feedback/test/integration.test.ts b/packages/feedback/test/integration.test.ts new file mode 100644 index 000000000000..2fed758b72bf --- /dev/null +++ b/packages/feedback/test/integration.test.ts @@ -0,0 +1,206 @@ +import * as SentryUtils from '@sentry/utils'; +import { + ACTOR_LABEL, + CANCEL_BUTTON_LABEL, + DEFAULT_THEME, + EMAIL_LABEL, + EMAIL_PLACEHOLDER, + FORM_TITLE, + MESSAGE_LABEL, + MESSAGE_PLACEHOLDER, + NAME_LABEL, + NAME_PLACEHOLDER, + SUBMIT_BUTTON_LABEL, + SUCCESS_MESSAGE_TEXT, +} from '../src/constants'; +import type { FeedbackInternalOptions } from '../src/types'; +import { sendFeedbackRequest } from '../src/util/sendFeedbackRequest'; +// import { createShadowHost } from '../../src/widget/createShadowHost'; +// import { createWidget } from '../../src/widget/createWidget'; +import { Feedback } from '../src/integration'; + +const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate); +jest.useFakeTimers(); + +jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); + +const DEFAULT_OPTIONS = { + id: 'sentry-feedback', + autoInject: true, + showEmail: true, + showName: true, + showBranding: false, + useSentryUser: { + email: 'email', + name: 'username', + }, + isAnonymous: false, + isEmailRequired: false, + isNameRequired: false, + + themeDark: DEFAULT_THEME.dark, + themeLight: DEFAULT_THEME.light, + colorScheme: 'system' as const, + + buttonLabel: ACTOR_LABEL, + cancelButtonLabel: CANCEL_BUTTON_LABEL, + submitButtonLabel: SUBMIT_BUTTON_LABEL, + formTitle: FORM_TITLE, + emailPlaceholder: EMAIL_PLACEHOLDER, + emailLabel: EMAIL_LABEL, + messagePlaceholder: MESSAGE_PLACEHOLDER, + messageLabel: MESSAGE_LABEL, + namePlaceholder: NAME_PLACEHOLDER, + nameLabel: NAME_LABEL, + successMessageText: SUCCESS_MESSAGE_TEXT, + + onActorClick: jest.fn(), + onDialogClose: jest.fn(), + onDialogOpen: jest.fn(), + onSubmitError: jest.fn(), + onSubmitSuccess: jest.fn(), +}; + +jest.mock('../src/util/sendFeedbackRequest', () => { + const original = jest.requireActual('../src/util/sendFeedbackRequest'); + return { + ...original, + sendFeedbackRequest: jest.fn(), + }; +}); + +// function createShadowAndWidget( +// feedbackOptions?: Partial & { shouldCreateActor?: boolean }, +// createWidgetOptions?: Partial[0]>, +// ) { +// const { shadow } = createShadowHost({ +// id: 'feedback', +// colorScheme: 'system', +// themeLight: DEFAULT_THEME.light, +// themeDark: DEFAULT_THEME.dark, +// }); +// const widget = createWidget({ +// shadow, +// options: { +// ...DEFAULT_OPTIONS, +// ...feedbackOptions, +// }, +// ...createWidgetOptions, +// }); +// +// return { shadow, widget }; +// } + +describe('Feedback integration', () => { + let feedback: Feedback; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + if (feedback) { + feedback.remove(); + } + }); + + it('autoinjects widget with actor', () => { + feedback = new Feedback(); + feedback.setupOnce(); + const widget = feedback.getWidget(); + expect(widget?.actor?.el).toBeInstanceOf(HTMLButtonElement); + const actorEl = widget?.actor?.el as HTMLButtonElement; + expect(actorEl.textContent).toBe(DEFAULT_OPTIONS.buttonLabel); + // No dialog until actor is clicked + expect(widget?.dialog).toBeUndefined(); + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(actorEl)).toBe(true); + }); + + it('does not create a widget with `autoInject: false`', () => { + feedback = new Feedback({ autoInject: false }); + feedback.setupOnce(); + const widget = feedback.getWidget(); + expect(widget?.actor?.el).toBeUndefined(); + // No dialog until actor is clicked + expect(widget?.dialog).toBeUndefined(); + }); + + it('opens (and closes) dialog when calling `openDialog` without injecting an actor', () => { + feedback = new Feedback({ autoInject: false }); + feedback.setupOnce(); + + let widget = feedback.getWidget(); + expect(widget?.actor?.el).toBeUndefined(); + // No dialog until actor is clicked + expect(widget?.dialog).toBeUndefined(); + + feedback.openDialog(); + widget = feedback.getWidget(); + expect(widget?.actor?.el).toBeUndefined(); + expect(widget?.dialog).not.toBeUndefined(); + expect(widget?.dialog?.checkIsOpen()).toBe(true); + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(widget.dialog.el)).toBe(true); + + feedback.closeDialog(); + expect(widget?.dialog?.checkIsOpen()).toBe(false); + }); + + it('attaches to a custom actor element', () => { + const onDialogOpen = jest.fn(); + // This element is in the normal DOM + const myActor = document.createElement('div'); + myActor.textContent = 'my button'; + + feedback = new Feedback({ autoInject: false }); + let widget = feedback.getWidget(); + expect(widget).toBe(null); + + feedback.attachTo(myActor, { onDialogOpen }); + + myActor.dispatchEvent(new Event('click')); + + widget = feedback.getWidget(); + + expect(widget?.dialog?.el).toBeInstanceOf(HTMLDialogElement); + expect(widget?.dialog?.el?.open).toBe(true); + expect(onDialogOpen).toHaveBeenCalledTimes(1); + // This is all we do with `attachTo` (open dialog) + }); + + it('creates multiple widgets and removes them', () => { + feedback = new Feedback({ autoInject: false }); + + feedback.createWidget(); + expect(feedback.getWidget()?.actor?.el).toBeInstanceOf(HTMLButtonElement); + // @ts-expect-error _widgets is private + expect(feedback._widgets.size).toBe(1); + + feedback.createWidget(); + // @ts-expect-error _widgets is private + expect(feedback._widgets.size).toBe(2); + + // @ts-expect-error _widgets is private + const widgets = Array.from(feedback._widgets.values()); + expect(widgets[0]).not.toEqual(widgets[1]); + + // Both actors will be in the DOM + + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(widgets[0].actor.el)).toBe(true); + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(widgets[1].actor.el)).toBe(true); + + feedback.removeWidget(widgets[0]); + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(widgets[0].actor.el)).toBe(false); + + feedback.removeWidget(widgets[1]); + // @ts-expect-error _shadow is private + expect(feedback._shadow.contains(widgets[1].actor.el)).toBe(false); + + // @ts-expect-error _widgets is private + expect(feedback._widgets.size).toBe(0); + }); +}); From 63f2550622d62638b72c4d5298835e3b3dec5da3 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 7 Nov 2023 08:18:07 -0500 Subject: [PATCH 3/4] lint --- packages/feedback/src/integration.ts | 1 - packages/feedback/test/integration.test.ts | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/feedback/src/integration.ts b/packages/feedback/src/integration.ts index cad6af1e16fc..b66021cef361 100644 --- a/packages/feedback/src/integration.ts +++ b/packages/feedback/src/integration.ts @@ -250,7 +250,6 @@ export class Feedback implements Integration { try { return this._createWidget(mergeOptions(this.options, optionOverrides || {})); } catch (err) { - console.error(err); logger.error(err); return null; } diff --git a/packages/feedback/test/integration.test.ts b/packages/feedback/test/integration.test.ts index 2fed758b72bf..cd95f7109b5d 100644 --- a/packages/feedback/test/integration.test.ts +++ b/packages/feedback/test/integration.test.ts @@ -1,4 +1,5 @@ import * as SentryUtils from '@sentry/utils'; + import { ACTOR_LABEL, CANCEL_BUTTON_LABEL, @@ -13,15 +14,8 @@ import { SUBMIT_BUTTON_LABEL, SUCCESS_MESSAGE_TEXT, } from '../src/constants'; -import type { FeedbackInternalOptions } from '../src/types'; -import { sendFeedbackRequest } from '../src/util/sendFeedbackRequest'; -// import { createShadowHost } from '../../src/widget/createShadowHost'; -// import { createWidget } from '../../src/widget/createWidget'; import { Feedback } from '../src/integration'; -const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate); -jest.useFakeTimers(); - jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); const DEFAULT_OPTIONS = { From 047b2bd0f679a7427e32b326778c20d9e7e6346f Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 7 Nov 2023 10:16:44 -0500 Subject: [PATCH 4/4] remove unused --- packages/feedback/test/integration.test.ts | 76 +--------------------- 1 file changed, 2 insertions(+), 74 deletions(-) diff --git a/packages/feedback/test/integration.test.ts b/packages/feedback/test/integration.test.ts index cd95f7109b5d..6857e6ce7897 100644 --- a/packages/feedback/test/integration.test.ts +++ b/packages/feedback/test/integration.test.ts @@ -1,60 +1,10 @@ import * as SentryUtils from '@sentry/utils'; -import { - ACTOR_LABEL, - CANCEL_BUTTON_LABEL, - DEFAULT_THEME, - EMAIL_LABEL, - EMAIL_PLACEHOLDER, - FORM_TITLE, - MESSAGE_LABEL, - MESSAGE_PLACEHOLDER, - NAME_LABEL, - NAME_PLACEHOLDER, - SUBMIT_BUTTON_LABEL, - SUCCESS_MESSAGE_TEXT, -} from '../src/constants'; +import { ACTOR_LABEL } from '../src/constants'; import { Feedback } from '../src/integration'; jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); -const DEFAULT_OPTIONS = { - id: 'sentry-feedback', - autoInject: true, - showEmail: true, - showName: true, - showBranding: false, - useSentryUser: { - email: 'email', - name: 'username', - }, - isAnonymous: false, - isEmailRequired: false, - isNameRequired: false, - - themeDark: DEFAULT_THEME.dark, - themeLight: DEFAULT_THEME.light, - colorScheme: 'system' as const, - - buttonLabel: ACTOR_LABEL, - cancelButtonLabel: CANCEL_BUTTON_LABEL, - submitButtonLabel: SUBMIT_BUTTON_LABEL, - formTitle: FORM_TITLE, - emailPlaceholder: EMAIL_PLACEHOLDER, - emailLabel: EMAIL_LABEL, - messagePlaceholder: MESSAGE_PLACEHOLDER, - messageLabel: MESSAGE_LABEL, - namePlaceholder: NAME_PLACEHOLDER, - nameLabel: NAME_LABEL, - successMessageText: SUCCESS_MESSAGE_TEXT, - - onActorClick: jest.fn(), - onDialogClose: jest.fn(), - onDialogOpen: jest.fn(), - onSubmitError: jest.fn(), - onSubmitSuccess: jest.fn(), -}; - jest.mock('../src/util/sendFeedbackRequest', () => { const original = jest.requireActual('../src/util/sendFeedbackRequest'); return { @@ -63,28 +13,6 @@ jest.mock('../src/util/sendFeedbackRequest', () => { }; }); -// function createShadowAndWidget( -// feedbackOptions?: Partial & { shouldCreateActor?: boolean }, -// createWidgetOptions?: Partial[0]>, -// ) { -// const { shadow } = createShadowHost({ -// id: 'feedback', -// colorScheme: 'system', -// themeLight: DEFAULT_THEME.light, -// themeDark: DEFAULT_THEME.dark, -// }); -// const widget = createWidget({ -// shadow, -// options: { -// ...DEFAULT_OPTIONS, -// ...feedbackOptions, -// }, -// ...createWidgetOptions, -// }); -// -// return { shadow, widget }; -// } - describe('Feedback integration', () => { let feedback: Feedback; @@ -104,7 +32,7 @@ describe('Feedback integration', () => { const widget = feedback.getWidget(); expect(widget?.actor?.el).toBeInstanceOf(HTMLButtonElement); const actorEl = widget?.actor?.el as HTMLButtonElement; - expect(actorEl.textContent).toBe(DEFAULT_OPTIONS.buttonLabel); + expect(actorEl.textContent).toBe(ACTOR_LABEL); // No dialog until actor is clicked expect(widget?.dialog).toBeUndefined(); // @ts-expect-error _shadow is private