makethisbetter 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -62
- package/README.md +4 -4
- package/dist/context/frustration.d.ts +1 -1
- package/dist/context/frustration.d.ts.map +1 -1
- package/dist/makethisbetter.cjs.js +13 -27
- package/dist/makethisbetter.cjs.js.map +1 -1
- package/dist/makethisbetter.esm.js +222 -247
- package/dist/makethisbetter.esm.js.map +1 -1
- package/dist/makethisbetter.js +16 -30
- package/dist/makethisbetter.js.map +1 -1
- package/dist/popup/clarify.d.ts +1 -0
- package/dist/popup/clarify.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/widget/controller.d.ts +0 -1
- package/dist/widget/controller.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/context/frustration.test.ts +2 -3
- package/src/context/frustration.ts +0 -1
- package/src/popup/clarify.test.ts +103 -0
- package/src/popup/clarify.ts +11 -3
- package/src/types.ts +2 -1
- package/src/widget/controller.test.ts +37 -0
- package/src/widget/controller.ts +12 -19
- package/src/widget/pet.test.ts +34 -32
package/dist/popup/clarify.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clarify.d.ts","sourceRoot":"","sources":["../../src/popup/clarify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAO9C,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB;IAmB7D,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"clarify.d.ts","sourceRoot":"","sources":["../../src/popup/clarify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAO9C,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB;IAmB7D,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;YAuBJ,iBAAiB;YAiBjB,WAAW;IAiCzB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;YAOL,IAAI;IAelB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,IAAI,IAAI;CAShB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface MakeThisBetterConfig {
|
|
2
2
|
projectKey: string;
|
|
3
|
-
entryMode?: 'button'
|
|
3
|
+
entryMode?: 'button';
|
|
4
4
|
locale?: string;
|
|
5
5
|
position?: 'left' | 'right';
|
|
6
6
|
theme?: 'light' | 'dark' | 'auto';
|
|
@@ -49,6 +49,7 @@ export interface FeedbackResponse {
|
|
|
49
49
|
id: string;
|
|
50
50
|
status: string;
|
|
51
51
|
project_id: string;
|
|
52
|
+
ai_clarify_available?: boolean;
|
|
52
53
|
}
|
|
53
54
|
export interface ClarifyMessage {
|
|
54
55
|
role: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE;QACL,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,IAAI,EAAE,OAAO,CAAA;CACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAA;AAIhE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,gBAAgB,CAAgC;gBAE5C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,uBAAuB;IA6B/B,OAAO,CAAC,SAAS;YAcH,YAAY;IAuC1B,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,OAAO;IAgBf,OAAO,IAAI,IAAI;CAWhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makethisbetter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Make This Better JS Widget SDK — embed user feedback collection in any website",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/makethisbetter.cjs.js",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"type": "git",
|
|
71
71
|
"url": "https://github.com/makethisbetter/makethisbetter-js.git"
|
|
72
72
|
},
|
|
73
|
-
"license": "
|
|
73
|
+
"license": "MIT"
|
|
74
74
|
}
|
|
@@ -112,15 +112,14 @@ describe('FrustrationDetector', () => {
|
|
|
112
112
|
document.body.removeChild(host)
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
it('does not directly emit console errors (only counts them as
|
|
115
|
+
it('does not directly emit console errors (only counts them as dead-click input)', () => {
|
|
116
116
|
makeDetector()
|
|
117
117
|
|
|
118
118
|
if (window.onerror) {
|
|
119
119
|
;(window.onerror as Function)('Test error', 'test.js', 1, 1, new Error('Test error'))
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
expect(errorEvents.length).toBe(0)
|
|
122
|
+
expect(events.length).toBe(0)
|
|
124
123
|
})
|
|
125
124
|
|
|
126
125
|
it('stops listening after stop()', () => {
|
|
@@ -395,6 +395,109 @@ describe('ClarifyCard', () => {
|
|
|
395
395
|
shadow.destroy()
|
|
396
396
|
})
|
|
397
397
|
|
|
398
|
+
it('ignores Enter during IME composition', async () => {
|
|
399
|
+
const startClarification = vi.fn()
|
|
400
|
+
.mockResolvedValueOnce({
|
|
401
|
+
status: 'pending',
|
|
402
|
+
messages: [{ role: 'assistant', content: 'Tell me more' }],
|
|
403
|
+
done: false,
|
|
404
|
+
} satisfies ClarifyResponse)
|
|
405
|
+
.mockResolvedValueOnce({
|
|
406
|
+
status: 'pending',
|
|
407
|
+
messages: [],
|
|
408
|
+
done: false,
|
|
409
|
+
} satisfies ClarifyResponse)
|
|
410
|
+
|
|
411
|
+
const { shadow, card } = setupCard({ startClarification })
|
|
412
|
+
await vi.waitFor(() => {
|
|
413
|
+
expect(startClarification).toHaveBeenCalledOnce()
|
|
414
|
+
})
|
|
415
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
416
|
+
|
|
417
|
+
const input = shadow.root.querySelector<HTMLInputElement>('.mtb-clarify-input')!
|
|
418
|
+
input.value = '导出坏了'
|
|
419
|
+
input.dispatchEvent(new Event('input'))
|
|
420
|
+
|
|
421
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', isComposing: true }))
|
|
422
|
+
expect(startClarification).toHaveBeenCalledOnce()
|
|
423
|
+
|
|
424
|
+
const legacyImeEnter = new KeyboardEvent('keydown', { key: 'Enter' })
|
|
425
|
+
Object.defineProperty(legacyImeEnter, 'keyCode', { value: 229 })
|
|
426
|
+
input.dispatchEvent(legacyImeEnter)
|
|
427
|
+
expect(startClarification).toHaveBeenCalledOnce()
|
|
428
|
+
|
|
429
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
430
|
+
await vi.waitFor(() => {
|
|
431
|
+
expect(startClarification).toHaveBeenCalledTimes(2)
|
|
432
|
+
})
|
|
433
|
+
expect(startClarification).toHaveBeenCalledWith('fb_123', '导出坏了')
|
|
434
|
+
|
|
435
|
+
card.destroy()
|
|
436
|
+
shadow.destroy()
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('keeps thinking indicator while waiting for the AI reply', async () => {
|
|
440
|
+
const startClarification = vi.fn().mockResolvedValue({
|
|
441
|
+
status: 'pending',
|
|
442
|
+
messages: [],
|
|
443
|
+
done: false,
|
|
444
|
+
} satisfies ClarifyResponse)
|
|
445
|
+
const getClarification = vi.fn().mockResolvedValue({
|
|
446
|
+
status: 'pending',
|
|
447
|
+
messages: [],
|
|
448
|
+
done: false,
|
|
449
|
+
} satisfies ClarifyResponse)
|
|
450
|
+
|
|
451
|
+
const { shadow, card } = setupCard({ startClarification, getClarification })
|
|
452
|
+
await vi.waitFor(() => {
|
|
453
|
+
expect(startClarification).toHaveBeenCalled()
|
|
454
|
+
})
|
|
455
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
456
|
+
|
|
457
|
+
expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeTruthy()
|
|
458
|
+
|
|
459
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
460
|
+
await vi.waitFor(() => {
|
|
461
|
+
expect(getClarification).toHaveBeenCalled()
|
|
462
|
+
})
|
|
463
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
464
|
+
expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeTruthy()
|
|
465
|
+
|
|
466
|
+
card.destroy()
|
|
467
|
+
shadow.destroy()
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('removes thinking indicator when the AI reply arrives', async () => {
|
|
471
|
+
const startClarification = vi.fn().mockResolvedValue({
|
|
472
|
+
status: 'pending',
|
|
473
|
+
messages: [],
|
|
474
|
+
done: false,
|
|
475
|
+
} satisfies ClarifyResponse)
|
|
476
|
+
const getClarification = vi.fn().mockResolvedValue({
|
|
477
|
+
status: 'pending',
|
|
478
|
+
messages: [{ role: 'assistant', content: 'What broke?' }],
|
|
479
|
+
done: false,
|
|
480
|
+
} satisfies ClarifyResponse)
|
|
481
|
+
|
|
482
|
+
const { shadow, card } = setupCard({ startClarification, getClarification })
|
|
483
|
+
await vi.waitFor(() => {
|
|
484
|
+
expect(startClarification).toHaveBeenCalled()
|
|
485
|
+
})
|
|
486
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
487
|
+
|
|
488
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
489
|
+
await vi.waitFor(() => {
|
|
490
|
+
expect(getClarification).toHaveBeenCalled()
|
|
491
|
+
})
|
|
492
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
493
|
+
|
|
494
|
+
expect(shadow.root.querySelector('.mtb-clarify-thinking')).toBeNull()
|
|
495
|
+
expect(shadow.root.querySelector('.mtb-clarify-ai')?.textContent).toBe('What broke?')
|
|
496
|
+
|
|
497
|
+
card.destroy()
|
|
498
|
+
shadow.destroy()
|
|
499
|
+
})
|
|
500
|
+
|
|
398
501
|
it('stops polling on destroy', async () => {
|
|
399
502
|
const getClarification = vi.fn().mockResolvedValue({
|
|
400
503
|
status: 'pending',
|
package/src/popup/clarify.ts
CHANGED
|
@@ -76,6 +76,8 @@ export class ClarifyCard {
|
|
|
76
76
|
})
|
|
77
77
|
|
|
78
78
|
this.inputEl.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
79
|
+
// keyCode 229: IME composition fallback for browsers without isComposing
|
|
80
|
+
if (e.isComposing || e.keyCode === 229) return
|
|
79
81
|
if (e.key === 'Enter') {
|
|
80
82
|
e.preventDefault()
|
|
81
83
|
this.sendMessage(m)
|
|
@@ -90,7 +92,7 @@ export class ClarifyCard {
|
|
|
90
92
|
try {
|
|
91
93
|
const res = await this.apiClient.startClarification(this.feedbackId)
|
|
92
94
|
if (this.destroyed) return
|
|
93
|
-
this.
|
|
95
|
+
this.renderConversation(res.messages, res.done)
|
|
94
96
|
if (res.done) {
|
|
95
97
|
this.handleDone()
|
|
96
98
|
} else {
|
|
@@ -118,7 +120,7 @@ export class ClarifyCard {
|
|
|
118
120
|
try {
|
|
119
121
|
const res = await this.apiClient.startClarification(this.feedbackId, text)
|
|
120
122
|
if (this.destroyed) return
|
|
121
|
-
this.
|
|
123
|
+
this.renderConversation(res.messages, res.done)
|
|
122
124
|
if (res.done) {
|
|
123
125
|
this.handleDone()
|
|
124
126
|
} else {
|
|
@@ -151,7 +153,7 @@ export class ClarifyCard {
|
|
|
151
153
|
try {
|
|
152
154
|
const res = await this.apiClient.getClarification(this.feedbackId)
|
|
153
155
|
if (this.destroyed) return
|
|
154
|
-
this.
|
|
156
|
+
this.renderConversation(res.messages, res.done)
|
|
155
157
|
if (res.done) {
|
|
156
158
|
this.stopPolling()
|
|
157
159
|
this.handleDone()
|
|
@@ -170,6 +172,12 @@ export class ClarifyCard {
|
|
|
170
172
|
}, DONE_DELAY_MS)
|
|
171
173
|
}
|
|
172
174
|
|
|
175
|
+
private renderConversation(messages: ClarifyMessage[], done: boolean): void {
|
|
176
|
+
this.renderMessages(messages)
|
|
177
|
+
const last = messages[messages.length - 1]
|
|
178
|
+
if (!done && (!last || last.role === 'user')) this.showThinking()
|
|
179
|
+
}
|
|
180
|
+
|
|
173
181
|
private renderMessages(messages: ClarifyMessage[]): void {
|
|
174
182
|
this.messagesEl.innerHTML = ''
|
|
175
183
|
for (const msg of messages) {
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface MakeThisBetterConfig {
|
|
2
2
|
projectKey: string
|
|
3
|
-
entryMode?: 'button'
|
|
3
|
+
entryMode?: 'button'
|
|
4
4
|
locale?: string
|
|
5
5
|
position?: 'left' | 'right'
|
|
6
6
|
theme?: 'light' | 'dark' | 'auto'
|
|
@@ -52,6 +52,7 @@ export interface FeedbackResponse {
|
|
|
52
52
|
id: string
|
|
53
53
|
status: string
|
|
54
54
|
project_id: string
|
|
55
|
+
ai_clarify_available?: boolean
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export interface ClarifyMessage {
|
|
@@ -61,4 +61,41 @@ describe('MakeThisBetter widget flow', () => {
|
|
|
61
61
|
expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Thanks!')
|
|
62
62
|
}, { timeout: 5000 })
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
it('skips the AI clarify chat when ai_clarify_available is false', async () => {
|
|
66
|
+
const target = document.createElement('button')
|
|
67
|
+
target.id = 'export-btn'
|
|
68
|
+
target.textContent = 'Export PDF'
|
|
69
|
+
document.body.appendChild(target)
|
|
70
|
+
|
|
71
|
+
document.elementsFromPoint = vi.fn(() => [target])
|
|
72
|
+
|
|
73
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
|
|
74
|
+
return new Response(JSON.stringify({ id: 'fb_1', status: 'open', project_id: 'proj_test', ai_clarify_available: false }), { status: 201 })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
MakeThisBetter.init({
|
|
78
|
+
projectKey: 'proj_test',
|
|
79
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const shadow = document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
83
|
+
shadow.querySelector<HTMLButtonElement>('.mtb-tab')!.click()
|
|
84
|
+
|
|
85
|
+
const overlay = shadow.querySelector<HTMLDivElement>('.mtb-overlay')!
|
|
86
|
+
overlay.dispatchEvent(new MouseEvent('mousedown', { clientX: 20, clientY: 30, bubbles: true }))
|
|
87
|
+
overlay.dispatchEvent(new MouseEvent('mouseup', { clientX: 20, clientY: 30, bubbles: true }))
|
|
88
|
+
|
|
89
|
+
const textarea = shadow.querySelector<HTMLTextAreaElement>('.mtb-textarea')!
|
|
90
|
+
textarea.value = 'Export is broken'
|
|
91
|
+
textarea.dispatchEvent(new Event('input'))
|
|
92
|
+
shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
|
|
93
|
+
|
|
94
|
+
await vi.waitFor(() => {
|
|
95
|
+
expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Thanks!')
|
|
96
|
+
}, { timeout: 5000 })
|
|
97
|
+
|
|
98
|
+
expect(shadow.querySelector('.mtb-clarify')).toBeNull()
|
|
99
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
100
|
+
})
|
|
64
101
|
})
|
package/src/widget/controller.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ShadowContainer } from './shadow'
|
|
2
2
|
import { FeedbackTab } from './tab'
|
|
3
|
-
import { PetEntry } from './pet'
|
|
4
3
|
import { FrustrationPromptCard } from './frustration-prompt'
|
|
5
4
|
import { AnnotationToolbar } from '../annotate/toolbar'
|
|
6
5
|
import type { ToolbarMode } from '../annotate/toolbar'
|
|
@@ -24,7 +23,6 @@ export class WidgetController {
|
|
|
24
23
|
private config: MakeThisBetterConfig
|
|
25
24
|
private shadow!: ShadowContainer
|
|
26
25
|
private tab: FeedbackTab | null = null
|
|
27
|
-
private pet: PetEntry | null = null
|
|
28
26
|
private toolbar: AnnotationToolbar | null = null
|
|
29
27
|
private session: AnnotationSession | null = null
|
|
30
28
|
private popup: CommentPopup | null = null
|
|
@@ -56,17 +54,13 @@ export class WidgetController {
|
|
|
56
54
|
private buildUI(): void {
|
|
57
55
|
this.shadow = new ShadowContainer(this.config.theme ?? 'auto')
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
() => this.handleTabClick(),
|
|
67
|
-
this.config.tabText,
|
|
68
|
-
)
|
|
69
|
-
}
|
|
57
|
+
this.tab = new FeedbackTab(
|
|
58
|
+
this.shadow,
|
|
59
|
+
this.messages,
|
|
60
|
+
this.config.position ?? 'right',
|
|
61
|
+
() => this.handleTabClick(),
|
|
62
|
+
this.config.tabText,
|
|
63
|
+
)
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
private handleTabClick(): void {
|
|
@@ -98,7 +92,6 @@ export class WidgetController {
|
|
|
98
92
|
private enterAnnotationMode(): void {
|
|
99
93
|
this.mode = 'annotating'
|
|
100
94
|
this.tab?.setActive(true)
|
|
101
|
-
this.pet?.setActive(true)
|
|
102
95
|
|
|
103
96
|
this.toolbar = new AnnotationToolbar(
|
|
104
97
|
this.shadow,
|
|
@@ -183,7 +176,11 @@ export class WidgetController {
|
|
|
183
176
|
this.session = null
|
|
184
177
|
this.recordingManager?.destroy()
|
|
185
178
|
this.recordingManager = null
|
|
186
|
-
|
|
179
|
+
if (feedback.ai_clarify_available === false) {
|
|
180
|
+
this.showSuccess(feedback.project_id)
|
|
181
|
+
} else {
|
|
182
|
+
this.showClarify(feedback.id, feedback.project_id)
|
|
183
|
+
}
|
|
187
184
|
} catch {
|
|
188
185
|
this.popup?.setLoading(false)
|
|
189
186
|
this.popup?.setError(this.messages.error.submit)
|
|
@@ -193,7 +190,6 @@ export class WidgetController {
|
|
|
193
190
|
private showClarify(feedbackId: string, projectId: string): void {
|
|
194
191
|
this.mode = 'idle'
|
|
195
192
|
this.tab?.setActive(false)
|
|
196
|
-
this.pet?.setActive(false)
|
|
197
193
|
this.clarify = new ClarifyCard(this.shadow, {
|
|
198
194
|
feedbackId,
|
|
199
195
|
apiClient: this.apiClient,
|
|
@@ -209,7 +205,6 @@ export class WidgetController {
|
|
|
209
205
|
private showSuccess(projectId: string): void {
|
|
210
206
|
this.mode = 'idle'
|
|
211
207
|
this.tab?.setActive(false)
|
|
212
|
-
this.pet?.setActive(false)
|
|
213
208
|
const baseUrl = this.config.apiUrl
|
|
214
209
|
? this.config.apiUrl.replace(/\/api\/v1\/?$/, '')
|
|
215
210
|
: 'https://makethisbetter.dev'
|
|
@@ -227,7 +222,6 @@ export class WidgetController {
|
|
|
227
222
|
private exitAll(): void {
|
|
228
223
|
this.mode = 'idle'
|
|
229
224
|
this.tab?.setActive(false)
|
|
230
|
-
this.pet?.setActive(false)
|
|
231
225
|
this.toolbar?.destroy()
|
|
232
226
|
this.toolbar = null
|
|
233
227
|
this.session?.destroy()
|
|
@@ -249,7 +243,6 @@ export class WidgetController {
|
|
|
249
243
|
this.clarify?.destroy()
|
|
250
244
|
this.success?.destroy()
|
|
251
245
|
this.tab?.destroy()
|
|
252
|
-
this.pet?.destroy()
|
|
253
246
|
this.recordingManager?.destroy()
|
|
254
247
|
this.shadow.destroy()
|
|
255
248
|
}
|
package/src/widget/pet.test.ts
CHANGED
|
@@ -1,56 +1,58 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { PetEntry } from './pet'
|
|
3
|
+
import { ShadowContainer } from './shadow'
|
|
4
|
+
import { getMessages } from '../i18n'
|
|
2
5
|
import { MakeThisBetter } from '../index'
|
|
3
6
|
|
|
4
7
|
vi.mock('html-to-image', () => ({
|
|
5
8
|
toPng: vi.fn(async () => 'data:image/png;base64,AA=='),
|
|
6
9
|
}))
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// PetEntry is kept in the repo for the phase-2 pet mode but is not wired into
|
|
12
|
+
// the widget build, so it is unit-tested directly instead of via init().
|
|
13
|
+
describe('PetEntry', () => {
|
|
14
|
+
let shadow: ShadowContainer
|
|
15
|
+
|
|
9
16
|
afterEach(() => {
|
|
10
|
-
|
|
11
|
-
vi.restoreAllMocks()
|
|
17
|
+
shadow?.destroy()
|
|
12
18
|
document.body.innerHTML = ''
|
|
13
19
|
})
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
apiUrl: 'https://api.example.com/api/v1',
|
|
20
|
-
})
|
|
21
|
+
function makePet(onClick: () => void = () => {}) {
|
|
22
|
+
shadow = new ShadowContainer('light')
|
|
23
|
+
return new PetEntry(shadow, getMessages('en'), onClick)
|
|
24
|
+
}
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
26
|
+
function shadowRoot() {
|
|
27
|
+
return document.getElementById('mtb-widget-host')!.shadowRoot!
|
|
28
|
+
}
|
|
26
29
|
|
|
27
|
-
it('
|
|
28
|
-
|
|
29
|
-
projectKey: 'proj_test',
|
|
30
|
-
entryMode: 'pet',
|
|
31
|
-
apiUrl: 'https://api.example.com/api/v1',
|
|
32
|
-
})
|
|
30
|
+
it('renders the pet element with a speech bubble', () => {
|
|
31
|
+
makePet()
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
const bubble =
|
|
33
|
+
expect(shadowRoot().querySelector('.mtb-pet')).toBeTruthy()
|
|
34
|
+
const bubble = shadowRoot().querySelector('.mtb-pet-bubble')
|
|
36
35
|
expect(bubble).toBeTruthy()
|
|
37
36
|
expect(bubble!.textContent).toBe('Need help?')
|
|
38
37
|
})
|
|
39
38
|
|
|
40
|
-
it('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
entryMode: 'pet',
|
|
44
|
-
apiUrl: 'https://api.example.com/api/v1',
|
|
45
|
-
})
|
|
39
|
+
it('invokes the click callback on click', () => {
|
|
40
|
+
const onClick = vi.fn()
|
|
41
|
+
makePet(onClick)
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
shadowRoot().querySelector<HTMLDivElement>('.mtb-pet')!.click()
|
|
44
|
+
expect(onClick).toHaveBeenCalledOnce()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('Widget entry', () => {
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
MakeThisBetter.destroy()
|
|
51
|
+
vi.restoreAllMocks()
|
|
52
|
+
document.body.innerHTML = ''
|
|
51
53
|
})
|
|
52
54
|
|
|
53
|
-
it('renders tab
|
|
55
|
+
it('renders the tab entry and never the pet', () => {
|
|
54
56
|
MakeThisBetter.init({
|
|
55
57
|
projectKey: 'proj_test',
|
|
56
58
|
apiUrl: 'https://api.example.com/api/v1',
|