@mushi-mushi/web 0.3.0 → 0.4.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/CODE_OF_CONDUCT.md +51 -0
- package/CONTRIBUTING.md +122 -0
- package/README.md +23 -1
- package/SECURITY.md +50 -0
- package/dist/test-utils.cjs +22 -0
- package/dist/test-utils.cjs.map +1 -0
- package/dist/test-utils.d.cts +66 -0
- package/dist/test-utils.d.ts +66 -0
- package/dist/test-utils.js +18 -0
- package/dist/test-utils.js.map +1 -0
- package/package.json +29 -5
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributor Covenant Code of Conduct
|
|
9
|
+
|
|
10
|
+
## Our Pledge
|
|
11
|
+
|
|
12
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
13
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
14
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
15
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
16
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
17
|
+
identity and orientation.
|
|
18
|
+
|
|
19
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
20
|
+
diverse, inclusive, and healthy community.
|
|
21
|
+
|
|
22
|
+
## Our Standards
|
|
23
|
+
|
|
24
|
+
Examples of behavior that contributes to a positive environment:
|
|
25
|
+
|
|
26
|
+
- Using welcoming and inclusive language
|
|
27
|
+
- Being respectful of differing viewpoints and experiences
|
|
28
|
+
- Gracefully accepting constructive criticism
|
|
29
|
+
- Focusing on what is best for the community
|
|
30
|
+
- Showing empathy towards other community members
|
|
31
|
+
|
|
32
|
+
Examples of unacceptable behavior:
|
|
33
|
+
|
|
34
|
+
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
35
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
36
|
+
- Public or private harassment
|
|
37
|
+
- Publishing others' private information without explicit permission
|
|
38
|
+
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
39
|
+
|
|
40
|
+
## Enforcement
|
|
41
|
+
|
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
43
|
+
reported to the project team at **security@mushimushi.dev**.
|
|
44
|
+
|
|
45
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
46
|
+
|
|
47
|
+
## Attribution
|
|
48
|
+
|
|
49
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
|
50
|
+
version 2.1, available at
|
|
51
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributing to Mushi Mushi
|
|
9
|
+
|
|
10
|
+
Thanks for wanting to help. Here's everything you need to get started.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- **Node.js >= 22** (see `.node-version`)
|
|
15
|
+
- **pnpm >= 10** — install with `corepack enable`
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/kensaurus/mushi-mushi.git
|
|
21
|
+
cd mushi-mushi
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm dev # Start all dev servers (admin on :6464)
|
|
30
|
+
pnpm test # Run Vitest across all packages
|
|
31
|
+
pnpm typecheck # TypeScript checks
|
|
32
|
+
pnpm lint # ESLint
|
|
33
|
+
pnpm format # Prettier
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Working on a single package
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd packages/core
|
|
40
|
+
pnpm dev # Watch mode
|
|
41
|
+
pnpm test # Tests for this package only
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Project Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
packages/
|
|
48
|
+
core/ Types, API client, offline queue (MIT)
|
|
49
|
+
web/ Browser SDK — widget, capture (MIT)
|
|
50
|
+
react/ React bindings (MIT)
|
|
51
|
+
vue/ Vue 3 plugin (MIT)
|
|
52
|
+
svelte/ Svelte SDK (MIT)
|
|
53
|
+
angular/ Angular SDK (MIT)
|
|
54
|
+
react-native/ React Native SDK (MIT)
|
|
55
|
+
cli/ CLI tool (MIT)
|
|
56
|
+
mcp/ MCP server for coding agents (MIT)
|
|
57
|
+
server/ Supabase Edge Functions (BSL)
|
|
58
|
+
agents/ Agentic fix pipeline (BSL)
|
|
59
|
+
verify/ Fix verification (BSL)
|
|
60
|
+
apps/
|
|
61
|
+
admin/ Admin dashboard (React + Tailwind)
|
|
62
|
+
docs/ Documentation site (planned)
|
|
63
|
+
tooling/
|
|
64
|
+
eslint-config/ Shared ESLint flat config
|
|
65
|
+
tsconfig/ Shared TypeScript configs
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Making Changes
|
|
69
|
+
|
|
70
|
+
1. Create a feature branch from `master`
|
|
71
|
+
2. Make your changes
|
|
72
|
+
3. Add tests for new functionality
|
|
73
|
+
4. Run `pnpm typecheck && pnpm lint && pnpm test` to verify
|
|
74
|
+
5. Create a changeset if your change affects published packages:
|
|
75
|
+
```bash
|
|
76
|
+
pnpm changeset
|
|
77
|
+
```
|
|
78
|
+
6. Open a pull request
|
|
79
|
+
|
|
80
|
+
## Changesets
|
|
81
|
+
|
|
82
|
+
We use [Changesets](https://github.com/changesets/changesets) for versioning. If your PR modifies a published package (`core`, `web`, `react`, `vue`, `svelte`, `angular`, `react-native`, `cli`, `mcp`), add a changeset:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm changeset
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Select the affected packages, the semver bump type, and write a summary. The changeset file gets committed with your PR.
|
|
89
|
+
|
|
90
|
+
## Code Style
|
|
91
|
+
|
|
92
|
+
- **TypeScript strict mode** — no `any` unless absolutely necessary
|
|
93
|
+
- **Prettier** formats everything — run `pnpm format` before committing
|
|
94
|
+
- **ESLint** catches bugs — `pnpm lint` must pass
|
|
95
|
+
- **No default exports** in library packages — use named exports
|
|
96
|
+
- **Dual ESM/CJS** builds via tsup for all SDK packages
|
|
97
|
+
|
|
98
|
+
## Commit Messages
|
|
99
|
+
|
|
100
|
+
Use conventional commits:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
feat(core): add batch report submission
|
|
104
|
+
fix(web): prevent widget from opening during screenshot
|
|
105
|
+
docs(react): update provider usage example
|
|
106
|
+
chore: bump dependencies
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Tests
|
|
110
|
+
|
|
111
|
+
- **Framework:** Vitest
|
|
112
|
+
- **Location:** Co-located with source (`src/foo.test.ts`)
|
|
113
|
+
- **Coverage:** Required for `core`, `web`, `react` — encouraged for all packages
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
- SDK packages are MIT — your contributions will be MIT-licensed
|
|
118
|
+
- Server/agents/verify are BSL 1.1 — contributions to those packages fall under BSL
|
|
119
|
+
|
|
120
|
+
## Questions?
|
|
121
|
+
|
|
122
|
+
Open an issue or start a discussion. We're happy to help.
|
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Each trigger respects its config flag — set `rageClick: false` to disable rage
|
|
|
46
46
|
|
|
47
47
|
## Bundle Size
|
|
48
48
|
|
|
49
|
-
~6 KB brotli
|
|
49
|
+
~6 KB brotli, enforced at **15 KB gzipped** via `size-limit` in CI. Requires `@mushi-mushi/core` as a dependency (not bundled inline). The `./test-utils` entry is a separate artifact and is never pulled into production bundles.
|
|
50
50
|
|
|
51
51
|
## Quick Start
|
|
52
52
|
|
|
@@ -99,6 +99,28 @@ setupProactiveTriggers({
|
|
|
99
99
|
});
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
## Test utilities (`./test-utils`)
|
|
103
|
+
|
|
104
|
+
Deterministic Playwright / jsdom helpers, published as a separate
|
|
105
|
+
entry-point so production bundles pay nothing for them:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { triggerBug, openReport, waitForQueueDrain } from '@mushi-mushi/web/test-utils';
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Export | Purpose |
|
|
112
|
+
|---------------------|-------------------------------------------------------------------------|
|
|
113
|
+
| `triggerBug(opts?)` | Submit a report bypassing the widget. Returns the server-assigned id. |
|
|
114
|
+
| `openReport(cat?)` | Open the widget programmatically without submitting. |
|
|
115
|
+
| `waitForQueueDrain` | Resolve once the offline queue is empty (number remaining at timeout). |
|
|
116
|
+
|
|
117
|
+
Every helper no-ops when `Mushi.getInstance()` returns `null`, so
|
|
118
|
+
conditional-wiring tests (e.g. cloud vs local targets) don't need to
|
|
119
|
+
branch. For browser-context use in Playwright's `page.evaluate`, import
|
|
120
|
+
the SDK via the app's own bundle (`window.__mushi__` in dev builds) or
|
|
121
|
+
POST to `/v1/reports` directly — `page.evaluate` has no npm resolver in
|
|
122
|
+
the browser context.
|
|
123
|
+
|
|
102
124
|
## License
|
|
103
125
|
|
|
104
126
|
MIT
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Security Policy
|
|
9
|
+
|
|
10
|
+
## Supported Versions
|
|
11
|
+
|
|
12
|
+
| Version | Supported |
|
|
13
|
+
|---------|-----------|
|
|
14
|
+
| 0.x | Yes |
|
|
15
|
+
|
|
16
|
+
## Reporting a Vulnerability
|
|
17
|
+
|
|
18
|
+
If you discover a security vulnerability, please report it responsibly.
|
|
19
|
+
|
|
20
|
+
**Do NOT open a public GitHub issue.**
|
|
21
|
+
|
|
22
|
+
Instead, email: **security@mushimushi.dev**
|
|
23
|
+
|
|
24
|
+
Include:
|
|
25
|
+
- Description of the vulnerability
|
|
26
|
+
- Steps to reproduce
|
|
27
|
+
- Impact assessment
|
|
28
|
+
- Suggested fix (if any)
|
|
29
|
+
|
|
30
|
+
We will acknowledge receipt within 48 hours and aim to release a patch within 7 days for critical issues.
|
|
31
|
+
|
|
32
|
+
## Scope
|
|
33
|
+
|
|
34
|
+
- All `@mushi-mushi/*` npm packages
|
|
35
|
+
- Supabase Edge Functions (server-side)
|
|
36
|
+
- Admin console application
|
|
37
|
+
- CLI tool
|
|
38
|
+
|
|
39
|
+
## Out of Scope
|
|
40
|
+
|
|
41
|
+
- Self-hosted deployments configured by the user
|
|
42
|
+
- Third-party integrations (Jira, Linear, PagerDuty)
|
|
43
|
+
- Vulnerabilities requiring physical access
|
|
44
|
+
|
|
45
|
+
## Security Best Practices for Users
|
|
46
|
+
|
|
47
|
+
- **Never commit your API keys** — use environment variables
|
|
48
|
+
- **Rotate API keys** regularly via the admin console
|
|
49
|
+
- **Enable SSO** for team projects (Enterprise tier)
|
|
50
|
+
- **Review audit logs** periodically for suspicious activity
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('@mushi-mushi/core');
|
|
4
|
+
|
|
5
|
+
// src/mushi.ts
|
|
6
|
+
|
|
7
|
+
// src/test-utils.ts
|
|
8
|
+
async function triggerBug(opts = {}) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function openReport(category) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
async function waitForQueueDrain(options = {}) {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.openReport = openReport;
|
|
19
|
+
exports.triggerBug = triggerBug;
|
|
20
|
+
exports.waitForQueueDrain = waitForQueueDrain;
|
|
21
|
+
//# sourceMappingURL=test-utils.cjs.map
|
|
22
|
+
//# sourceMappingURL=test-utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/test-utils.ts"],"names":[],"mappings":";;;;;;;AAsDA,eAAsB,UAAA,CAAW,IAAA,GAA0B,EAAC,EAA2B;AAErF,EAAU,OAAO,IAAA;AAqBnB;AAOO,SAAS,WAAW,QAAA,EAAsC;AAE/D,EAAU;AAEZ;AAOA,eAAsB,iBAAA,CAAkB,OAAA,GAAkC,EAAC,EAAoB;AAE7F,EAAU,OAAO,CAAA;AAenB","file":"test-utils.cjs","sourcesContent":["/**\n * @mushi-mushi/web/test-utils\n *\n * Playwright / jsdom helpers for deterministic SDK tests. This module is\n * published as a separate entry-point so production bundles never pay the\n * cost — import it via `import { triggerBug, openReport } from\n * '@mushi-mushi/web/test-utils'`.\n *\n * Design goals:\n * 1. Zero production footprint. Nothing here is imported from `./mushi`,\n * `./widget`, or any runtime module. We reach for the live SDK via\n * `Mushi.getInstance()` so the test process sees exactly what the app\n * bootstrapped — no duplicate SDK singletons, no double-init races.\n * 2. Safe in a test harness even when Mushi is disabled. Every helper\n * no-ops when `Mushi.getInstance()` returns `null`, so Playwright\n * specs that conditionally wire Mushi (e.g. cloud vs local targets)\n * don't have to branch.\n * 3. Flat surface. Tests want 3 verbs: \"open the widget\", \"submit a\n * report programmatically\", \"wait until the SDK confirms the POST\n * landed\". Anything beyond that belongs in the app under test.\n */\n\nimport { Mushi } from './mushi';\nimport type {\n MushiReportCategory,\n MushiSDKInstance,\n} from '@mushi-mushi/core';\n\n/** Options accepted by `triggerBug`. Mirrors the core report contract but\n * keeps every field optional so a Playwright test can say\n * `triggerBug({ description: 'button dead' })` without a category. */\nexport interface TriggerBugOptions {\n /** Short free-text description of the issue. Defaults to a marker\n * string so tests that forget to pass a description still produce a\n * distinguishable report in the admin console. */\n description?: string;\n /** Severity-free category tag. */\n category?: MushiReportCategory;\n /** Arbitrary metadata the test wants to round-trip. Merged on top of\n * whatever `setMetadata` has already stored. */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Submit a report bypassing the widget UI. Returns the server-assigned\n * `reportId` when the POST lands, or `null` if Mushi isn't initialised in\n * this test context (or the submit failed — the SDK swallows network\n * errors silently by design).\n *\n * Use this from Playwright `page.evaluate(...)` calls to round-trip a\n * report into the backend without needing to drive the widget's DOM. It's\n * the fastest way to assert \"a bug report made it from the browser into\n * reports/ in Supabase\".\n */\nexport async function triggerBug(opts: TriggerBugOptions = {}): Promise<string | null> {\n const sdk = Mushi.getInstance();\n if (!sdk) return null;\n\n const description = opts.description\n ?? `[test-utils] triggerBug marker ${new Date().toISOString()}`;\n\n if (opts.metadata) {\n for (const [k, v] of Object.entries(opts.metadata)) {\n try { sdk.setMetadata(k, v); } catch { /* ignore */ }\n }\n }\n\n // `captureEvent` is the documented programmatic-submit API since 0.3.0\n // — it bypasses the widget, resolves with the server-assigned report\n // id, and participates in the same offline queue / pre-filter pipeline\n // as a widget submission.\n return await sdk.captureEvent({\n description,\n source: 'test-utils',\n ...(opts.category ? { category: opts.category } : {}),\n ...(opts.metadata ? { metadata: opts.metadata } : {}),\n });\n}\n\n/**\n * Programmatically open the Mushi widget without submitting anything. Use\n * for interaction tests that want to assert \"the widget is mounted and\n * reachable\" or to drive a user-like flow via Playwright selectors.\n */\nexport function openReport(category?: MushiReportCategory): void {\n const sdk = Mushi.getInstance();\n if (!sdk) return;\n sdk.report(category ? { category } : undefined);\n}\n\n/**\n * Wait until the offline queue drains — useful after `triggerBug` in tests\n * that submit while offline then assert the report eventually syncs.\n * Resolves with the number of queued items remaining (0 = fully drained).\n */\nexport async function waitForQueueDrain(options: { timeoutMs?: number } = {}): Promise<number> {\n const sdk = Mushi.getInstance();\n if (!sdk) return 0;\n const timeoutMs = options.timeoutMs ?? 5_000;\n const started = Date.now();\n // The SDK instance doesn't publicly expose the queue, but `getQueueSize`\n // has been in the contract since 0.2.x. We tolerate its absence so\n // upgrading doesn't break tests that only need triggerBug/openReport.\n const getQueueSize = (sdk as MushiSDKInstance & { getQueueSize?: () => number }).getQueueSize;\n if (typeof getQueueSize !== 'function') return 0;\n\n while (Date.now() - started < timeoutMs) {\n const remaining = getQueueSize.call(sdk);\n if (remaining === 0) return 0;\n await new Promise((r) => setTimeout(r, 100));\n }\n return getQueueSize.call(sdk);\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MushiReportCategory } from '@mushi-mushi/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @mushi-mushi/web/test-utils
|
|
5
|
+
*
|
|
6
|
+
* Playwright / jsdom helpers for deterministic SDK tests. This module is
|
|
7
|
+
* published as a separate entry-point so production bundles never pay the
|
|
8
|
+
* cost — import it via `import { triggerBug, openReport } from
|
|
9
|
+
* '@mushi-mushi/web/test-utils'`.
|
|
10
|
+
*
|
|
11
|
+
* Design goals:
|
|
12
|
+
* 1. Zero production footprint. Nothing here is imported from `./mushi`,
|
|
13
|
+
* `./widget`, or any runtime module. We reach for the live SDK via
|
|
14
|
+
* `Mushi.getInstance()` so the test process sees exactly what the app
|
|
15
|
+
* bootstrapped — no duplicate SDK singletons, no double-init races.
|
|
16
|
+
* 2. Safe in a test harness even when Mushi is disabled. Every helper
|
|
17
|
+
* no-ops when `Mushi.getInstance()` returns `null`, so Playwright
|
|
18
|
+
* specs that conditionally wire Mushi (e.g. cloud vs local targets)
|
|
19
|
+
* don't have to branch.
|
|
20
|
+
* 3. Flat surface. Tests want 3 verbs: "open the widget", "submit a
|
|
21
|
+
* report programmatically", "wait until the SDK confirms the POST
|
|
22
|
+
* landed". Anything beyond that belongs in the app under test.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Options accepted by `triggerBug`. Mirrors the core report contract but
|
|
26
|
+
* keeps every field optional so a Playwright test can say
|
|
27
|
+
* `triggerBug({ description: 'button dead' })` without a category. */
|
|
28
|
+
interface TriggerBugOptions {
|
|
29
|
+
/** Short free-text description of the issue. Defaults to a marker
|
|
30
|
+
* string so tests that forget to pass a description still produce a
|
|
31
|
+
* distinguishable report in the admin console. */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** Severity-free category tag. */
|
|
34
|
+
category?: MushiReportCategory;
|
|
35
|
+
/** Arbitrary metadata the test wants to round-trip. Merged on top of
|
|
36
|
+
* whatever `setMetadata` has already stored. */
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Submit a report bypassing the widget UI. Returns the server-assigned
|
|
41
|
+
* `reportId` when the POST lands, or `null` if Mushi isn't initialised in
|
|
42
|
+
* this test context (or the submit failed — the SDK swallows network
|
|
43
|
+
* errors silently by design).
|
|
44
|
+
*
|
|
45
|
+
* Use this from Playwright `page.evaluate(...)` calls to round-trip a
|
|
46
|
+
* report into the backend without needing to drive the widget's DOM. It's
|
|
47
|
+
* the fastest way to assert "a bug report made it from the browser into
|
|
48
|
+
* reports/ in Supabase".
|
|
49
|
+
*/
|
|
50
|
+
declare function triggerBug(opts?: TriggerBugOptions): Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Programmatically open the Mushi widget without submitting anything. Use
|
|
53
|
+
* for interaction tests that want to assert "the widget is mounted and
|
|
54
|
+
* reachable" or to drive a user-like flow via Playwright selectors.
|
|
55
|
+
*/
|
|
56
|
+
declare function openReport(category?: MushiReportCategory): void;
|
|
57
|
+
/**
|
|
58
|
+
* Wait until the offline queue drains — useful after `triggerBug` in tests
|
|
59
|
+
* that submit while offline then assert the report eventually syncs.
|
|
60
|
+
* Resolves with the number of queued items remaining (0 = fully drained).
|
|
61
|
+
*/
|
|
62
|
+
declare function waitForQueueDrain(options?: {
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}): Promise<number>;
|
|
65
|
+
|
|
66
|
+
export { type TriggerBugOptions, openReport, triggerBug, waitForQueueDrain };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MushiReportCategory } from '@mushi-mushi/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @mushi-mushi/web/test-utils
|
|
5
|
+
*
|
|
6
|
+
* Playwright / jsdom helpers for deterministic SDK tests. This module is
|
|
7
|
+
* published as a separate entry-point so production bundles never pay the
|
|
8
|
+
* cost — import it via `import { triggerBug, openReport } from
|
|
9
|
+
* '@mushi-mushi/web/test-utils'`.
|
|
10
|
+
*
|
|
11
|
+
* Design goals:
|
|
12
|
+
* 1. Zero production footprint. Nothing here is imported from `./mushi`,
|
|
13
|
+
* `./widget`, or any runtime module. We reach for the live SDK via
|
|
14
|
+
* `Mushi.getInstance()` so the test process sees exactly what the app
|
|
15
|
+
* bootstrapped — no duplicate SDK singletons, no double-init races.
|
|
16
|
+
* 2. Safe in a test harness even when Mushi is disabled. Every helper
|
|
17
|
+
* no-ops when `Mushi.getInstance()` returns `null`, so Playwright
|
|
18
|
+
* specs that conditionally wire Mushi (e.g. cloud vs local targets)
|
|
19
|
+
* don't have to branch.
|
|
20
|
+
* 3. Flat surface. Tests want 3 verbs: "open the widget", "submit a
|
|
21
|
+
* report programmatically", "wait until the SDK confirms the POST
|
|
22
|
+
* landed". Anything beyond that belongs in the app under test.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Options accepted by `triggerBug`. Mirrors the core report contract but
|
|
26
|
+
* keeps every field optional so a Playwright test can say
|
|
27
|
+
* `triggerBug({ description: 'button dead' })` without a category. */
|
|
28
|
+
interface TriggerBugOptions {
|
|
29
|
+
/** Short free-text description of the issue. Defaults to a marker
|
|
30
|
+
* string so tests that forget to pass a description still produce a
|
|
31
|
+
* distinguishable report in the admin console. */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** Severity-free category tag. */
|
|
34
|
+
category?: MushiReportCategory;
|
|
35
|
+
/** Arbitrary metadata the test wants to round-trip. Merged on top of
|
|
36
|
+
* whatever `setMetadata` has already stored. */
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Submit a report bypassing the widget UI. Returns the server-assigned
|
|
41
|
+
* `reportId` when the POST lands, or `null` if Mushi isn't initialised in
|
|
42
|
+
* this test context (or the submit failed — the SDK swallows network
|
|
43
|
+
* errors silently by design).
|
|
44
|
+
*
|
|
45
|
+
* Use this from Playwright `page.evaluate(...)` calls to round-trip a
|
|
46
|
+
* report into the backend without needing to drive the widget's DOM. It's
|
|
47
|
+
* the fastest way to assert "a bug report made it from the browser into
|
|
48
|
+
* reports/ in Supabase".
|
|
49
|
+
*/
|
|
50
|
+
declare function triggerBug(opts?: TriggerBugOptions): Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Programmatically open the Mushi widget without submitting anything. Use
|
|
53
|
+
* for interaction tests that want to assert "the widget is mounted and
|
|
54
|
+
* reachable" or to drive a user-like flow via Playwright selectors.
|
|
55
|
+
*/
|
|
56
|
+
declare function openReport(category?: MushiReportCategory): void;
|
|
57
|
+
/**
|
|
58
|
+
* Wait until the offline queue drains — useful after `triggerBug` in tests
|
|
59
|
+
* that submit while offline then assert the report eventually syncs.
|
|
60
|
+
* Resolves with the number of queued items remaining (0 = fully drained).
|
|
61
|
+
*/
|
|
62
|
+
declare function waitForQueueDrain(options?: {
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}): Promise<number>;
|
|
65
|
+
|
|
66
|
+
export { type TriggerBugOptions, openReport, triggerBug, waitForQueueDrain };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import '@mushi-mushi/core';
|
|
2
|
+
|
|
3
|
+
// src/mushi.ts
|
|
4
|
+
|
|
5
|
+
// src/test-utils.ts
|
|
6
|
+
async function triggerBug(opts = {}) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
function openReport(category) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
async function waitForQueueDrain(options = {}) {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { openReport, triggerBug, waitForQueueDrain };
|
|
17
|
+
//# sourceMappingURL=test-utils.js.map
|
|
18
|
+
//# sourceMappingURL=test-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/test-utils.ts"],"names":[],"mappings":";;;;;AAsDA,eAAsB,UAAA,CAAW,IAAA,GAA0B,EAAC,EAA2B;AAErF,EAAU,OAAO,IAAA;AAqBnB;AAOO,SAAS,WAAW,QAAA,EAAsC;AAE/D,EAAU;AAEZ;AAOA,eAAsB,iBAAA,CAAkB,OAAA,GAAkC,EAAC,EAAoB;AAE7F,EAAU,OAAO,CAAA;AAenB","file":"test-utils.js","sourcesContent":["/**\n * @mushi-mushi/web/test-utils\n *\n * Playwright / jsdom helpers for deterministic SDK tests. This module is\n * published as a separate entry-point so production bundles never pay the\n * cost — import it via `import { triggerBug, openReport } from\n * '@mushi-mushi/web/test-utils'`.\n *\n * Design goals:\n * 1. Zero production footprint. Nothing here is imported from `./mushi`,\n * `./widget`, or any runtime module. We reach for the live SDK via\n * `Mushi.getInstance()` so the test process sees exactly what the app\n * bootstrapped — no duplicate SDK singletons, no double-init races.\n * 2. Safe in a test harness even when Mushi is disabled. Every helper\n * no-ops when `Mushi.getInstance()` returns `null`, so Playwright\n * specs that conditionally wire Mushi (e.g. cloud vs local targets)\n * don't have to branch.\n * 3. Flat surface. Tests want 3 verbs: \"open the widget\", \"submit a\n * report programmatically\", \"wait until the SDK confirms the POST\n * landed\". Anything beyond that belongs in the app under test.\n */\n\nimport { Mushi } from './mushi';\nimport type {\n MushiReportCategory,\n MushiSDKInstance,\n} from '@mushi-mushi/core';\n\n/** Options accepted by `triggerBug`. Mirrors the core report contract but\n * keeps every field optional so a Playwright test can say\n * `triggerBug({ description: 'button dead' })` without a category. */\nexport interface TriggerBugOptions {\n /** Short free-text description of the issue. Defaults to a marker\n * string so tests that forget to pass a description still produce a\n * distinguishable report in the admin console. */\n description?: string;\n /** Severity-free category tag. */\n category?: MushiReportCategory;\n /** Arbitrary metadata the test wants to round-trip. Merged on top of\n * whatever `setMetadata` has already stored. */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Submit a report bypassing the widget UI. Returns the server-assigned\n * `reportId` when the POST lands, or `null` if Mushi isn't initialised in\n * this test context (or the submit failed — the SDK swallows network\n * errors silently by design).\n *\n * Use this from Playwright `page.evaluate(...)` calls to round-trip a\n * report into the backend without needing to drive the widget's DOM. It's\n * the fastest way to assert \"a bug report made it from the browser into\n * reports/ in Supabase\".\n */\nexport async function triggerBug(opts: TriggerBugOptions = {}): Promise<string | null> {\n const sdk = Mushi.getInstance();\n if (!sdk) return null;\n\n const description = opts.description\n ?? `[test-utils] triggerBug marker ${new Date().toISOString()}`;\n\n if (opts.metadata) {\n for (const [k, v] of Object.entries(opts.metadata)) {\n try { sdk.setMetadata(k, v); } catch { /* ignore */ }\n }\n }\n\n // `captureEvent` is the documented programmatic-submit API since 0.3.0\n // — it bypasses the widget, resolves with the server-assigned report\n // id, and participates in the same offline queue / pre-filter pipeline\n // as a widget submission.\n return await sdk.captureEvent({\n description,\n source: 'test-utils',\n ...(opts.category ? { category: opts.category } : {}),\n ...(opts.metadata ? { metadata: opts.metadata } : {}),\n });\n}\n\n/**\n * Programmatically open the Mushi widget without submitting anything. Use\n * for interaction tests that want to assert \"the widget is mounted and\n * reachable\" or to drive a user-like flow via Playwright selectors.\n */\nexport function openReport(category?: MushiReportCategory): void {\n const sdk = Mushi.getInstance();\n if (!sdk) return;\n sdk.report(category ? { category } : undefined);\n}\n\n/**\n * Wait until the offline queue drains — useful after `triggerBug` in tests\n * that submit while offline then assert the report eventually syncs.\n * Resolves with the number of queued items remaining (0 = fully drained).\n */\nexport async function waitForQueueDrain(options: { timeoutMs?: number } = {}): Promise<number> {\n const sdk = Mushi.getInstance();\n if (!sdk) return 0;\n const timeoutMs = options.timeoutMs ?? 5_000;\n const started = Date.now();\n // The SDK instance doesn't publicly expose the queue, but `getQueueSize`\n // has been in the contract since 0.2.x. We tolerate its absence so\n // upgrading doesn't break tests that only need triggerBug/openReport.\n const getQueueSize = (sdk as MushiSDKInstance & { getQueueSize?: () => number }).getQueueSize;\n if (typeof getQueueSize !== 'function') return 0;\n\n while (Date.now() - started < timeoutMs) {\n const remaining = getQueueSize.call(sdk);\n if (remaining === 0) return 0;\n await new Promise((r) => setTimeout(r, 100));\n }\n return getQueueSize.call(sdk);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Mushi Mushi browser SDK — embeddable bug reporting widget with Shadow DOM isolation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,12 +17,25 @@
|
|
|
17
17
|
"types": "./dist/index.d.cts",
|
|
18
18
|
"default": "./dist/index.cjs"
|
|
19
19
|
}
|
|
20
|
+
},
|
|
21
|
+
"./test-utils": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/test-utils.d.ts",
|
|
24
|
+
"default": "./dist/test-utils.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/test-utils.d.cts",
|
|
28
|
+
"default": "./dist/test-utils.cjs"
|
|
29
|
+
}
|
|
20
30
|
}
|
|
21
31
|
},
|
|
22
32
|
"files": [
|
|
23
33
|
"dist",
|
|
24
34
|
"README.md",
|
|
25
|
-
"LICENSE"
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"CONTRIBUTING.md",
|
|
37
|
+
"CODE_OF_CONDUCT.md",
|
|
38
|
+
"SECURITY.md"
|
|
26
39
|
],
|
|
27
40
|
"repository": {
|
|
28
41
|
"type": "git",
|
|
@@ -53,17 +66,25 @@
|
|
|
53
66
|
"sdk"
|
|
54
67
|
],
|
|
55
68
|
"publishConfig": {
|
|
56
|
-
"access": "public"
|
|
69
|
+
"access": "public",
|
|
70
|
+
"provenance": true
|
|
57
71
|
},
|
|
58
72
|
"sideEffects": false,
|
|
59
73
|
"size-limit": [
|
|
60
74
|
{
|
|
75
|
+
"name": "Core SDK bundle (minified + gzipped)",
|
|
61
76
|
"path": "dist/index.js",
|
|
62
|
-
"limit": "
|
|
77
|
+
"limit": "15 KB",
|
|
78
|
+
"gzip": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "Core SDK bundle (uncompressed)",
|
|
82
|
+
"path": "dist/index.js",
|
|
83
|
+
"limit": "60 KB"
|
|
63
84
|
}
|
|
64
85
|
],
|
|
65
86
|
"dependencies": {
|
|
66
|
-
"@mushi-mushi/core": "^0.
|
|
87
|
+
"@mushi-mushi/core": "^0.4.0"
|
|
67
88
|
},
|
|
68
89
|
"devDependencies": {
|
|
69
90
|
"@size-limit/file": "^12.1.0",
|
|
@@ -77,6 +98,9 @@
|
|
|
77
98
|
"@mushi-mushi/tsconfig": "0.0.0"
|
|
78
99
|
},
|
|
79
100
|
"author": "Kenji Sakuramoto",
|
|
101
|
+
"engines": {
|
|
102
|
+
"node": ">=20"
|
|
103
|
+
},
|
|
80
104
|
"scripts": {
|
|
81
105
|
"build": "tsup",
|
|
82
106
|
"dev": "tsup --watch",
|