browser-extension-manager 1.3.49 → 1.5.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/README.md +112 -70
- package/dist/background.js +6 -1
- package/dist/build.js +5 -7
- package/dist/cli.js +1 -0
- package/dist/commands/install.js +1 -1
- package/dist/commands/test.js +55 -0
- package/dist/content.js +4 -0
- package/dist/defaults/CHANGELOG.md +15 -0
- package/dist/defaults/CLAUDE.md +82 -6
- package/dist/defaults/docs/README.md +17 -0
- package/dist/defaults/test/README.md +32 -0
- package/dist/defaults/test/_init.js +10 -0
- package/dist/gulp/tasks/defaults.js +15 -2
- package/dist/offscreen.js +4 -0
- package/dist/options.js +4 -0
- package/dist/page.js +4 -0
- package/dist/popup.js +4 -0
- package/dist/sidepanel.js +4 -0
- package/dist/test/assert.js +120 -0
- package/dist/test/fixtures/consumer-extension/dist/background.js +15 -0
- package/dist/test/fixtures/consumer-extension/dist/manifest.json +20 -0
- package/dist/test/fixtures/consumer-extension/dist/options.html +10 -0
- package/dist/test/fixtures/consumer-extension/dist/popup.html +11 -0
- package/dist/test/harness/extension/background.js +26 -0
- package/dist/test/harness/extension/manifest.json +27 -0
- package/dist/test/harness/extension/options.html +12 -0
- package/dist/test/harness/extension/popup.html +12 -0
- package/dist/test/harness/extension/sidepanel.html +12 -0
- package/dist/test/index.js +63 -0
- package/dist/test/runner.js +469 -0
- package/dist/test/runners/boot.js +201 -0
- package/dist/test/runners/chromium.js +399 -0
- package/dist/test/suites/background/messaging.test.js +42 -0
- package/dist/test/suites/background/storage.test.js +44 -0
- package/dist/test/suites/background/sw-context.test.js +47 -0
- package/dist/test/suites/boot/extension-loads.test.js +56 -0
- package/dist/test/suites/build/affiliatizer.test.js +63 -0
- package/dist/test/suites/build/cli.test.js +123 -0
- package/dist/test/suites/build/expect.test.js +47 -0
- package/dist/test/suites/build/exports.test.js +52 -0
- package/dist/test/suites/build/extension-fallback.test.js +41 -0
- package/dist/test/suites/build/logger-lite.test.js +59 -0
- package/dist/test/suites/build/manager.test.js +184 -0
- package/dist/test/suites/build/mode-helpers.test.js +135 -0
- package/dist/test/suites/view/options-and-sidepanel.test.js +14 -0
- package/dist/test/suites/view/popup-context.test.js +51 -0
- package/dist/test/suites/view/sidepanel.test.js +14 -0
- package/dist/utils/mode-helpers.js +117 -0
- package/package.json +7 -4
package/dist/popup.js
CHANGED
|
@@ -3,6 +3,7 @@ import webManager from 'web-manager';
|
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
5
|
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
|
+
import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
|
|
6
7
|
|
|
7
8
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
9
|
import '__theme__/_theme.js';
|
|
@@ -59,5 +60,8 @@ class Manager {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
|
|
64
|
+
attachModeHelpers(Manager);
|
|
65
|
+
|
|
62
66
|
// Export
|
|
63
67
|
export default Manager;
|
package/dist/sidepanel.js
CHANGED
|
@@ -3,6 +3,7 @@ import webManager from 'web-manager';
|
|
|
3
3
|
import extension from './lib/extension.js';
|
|
4
4
|
import LoggerLite from './lib/logger-lite.js';
|
|
5
5
|
import { syncWithBackground, setupAuthBroadcastListener, setupSignOutListener, setupAuthEventListeners, openAuthPage as openAuthPageHelper } from './lib/auth-helpers.js';
|
|
6
|
+
import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
|
|
6
7
|
|
|
7
8
|
// Import theme (exposes Bootstrap to window.bootstrap)
|
|
8
9
|
import '__theme__/_theme.js';
|
|
@@ -59,5 +60,8 @@ class Manager {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
|
|
64
|
+
attachModeHelpers(Manager);
|
|
65
|
+
|
|
62
66
|
// Export
|
|
63
67
|
export default Manager;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Tiny expect() — Jest/Vitest-compatible subset.
|
|
2
|
+
// Each matcher throws on failure; the runner catches.
|
|
3
|
+
|
|
4
|
+
function deepEqual(a, b) {
|
|
5
|
+
if (a === b) return true;
|
|
6
|
+
if (typeof a !== typeof b) return false;
|
|
7
|
+
if (a === null || b === null) return a === b;
|
|
8
|
+
if (typeof a !== 'object') return false;
|
|
9
|
+
|
|
10
|
+
if (Array.isArray(a)) {
|
|
11
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
12
|
+
return a.every((x, i) => deepEqual(x, b[i]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ka = Object.keys(a);
|
|
16
|
+
const kb = Object.keys(b);
|
|
17
|
+
if (ka.length !== kb.length) return false;
|
|
18
|
+
return ka.every((k) => deepEqual(a[k], b[k]));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fmt(v) {
|
|
22
|
+
if (typeof v === 'string') return JSON.stringify(v);
|
|
23
|
+
if (v === undefined) return 'undefined';
|
|
24
|
+
try {
|
|
25
|
+
return JSON.stringify(v);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return String(v);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fail(message) {
|
|
32
|
+
const err = new Error(message);
|
|
33
|
+
err.name = 'AssertionError';
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildMatchers(actual, negated) {
|
|
38
|
+
const not = negated ? 'not ' : '';
|
|
39
|
+
|
|
40
|
+
function check(cond, message) {
|
|
41
|
+
if (negated) cond = !cond;
|
|
42
|
+
if (!cond) fail(message);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
toBe(expected) {
|
|
47
|
+
check(actual === expected, `expected ${fmt(actual)} ${not}to be ${fmt(expected)}`);
|
|
48
|
+
},
|
|
49
|
+
toEqual(expected) {
|
|
50
|
+
check(deepEqual(actual, expected), `expected ${fmt(actual)} ${not}to deeply equal ${fmt(expected)}`);
|
|
51
|
+
},
|
|
52
|
+
toBeTruthy() {
|
|
53
|
+
check(!!actual, `expected ${fmt(actual)} ${not}to be truthy`);
|
|
54
|
+
},
|
|
55
|
+
toBeFalsy() {
|
|
56
|
+
check(!actual, `expected ${fmt(actual)} ${not}to be falsy`);
|
|
57
|
+
},
|
|
58
|
+
toBeDefined() {
|
|
59
|
+
check(actual !== undefined, `expected ${fmt(actual)} ${not}to be defined`);
|
|
60
|
+
},
|
|
61
|
+
toBeUndefined() {
|
|
62
|
+
check(actual === undefined, `expected ${fmt(actual)} ${not}to be undefined`);
|
|
63
|
+
},
|
|
64
|
+
toBeNull() {
|
|
65
|
+
check(actual === null, `expected ${fmt(actual)} ${not}to be null`);
|
|
66
|
+
},
|
|
67
|
+
toContain(item) {
|
|
68
|
+
const has = Array.isArray(actual)
|
|
69
|
+
? actual.includes(item)
|
|
70
|
+
: (typeof actual === 'string' && actual.includes(item));
|
|
71
|
+
check(has, `expected ${fmt(actual)} ${not}to contain ${fmt(item)}`);
|
|
72
|
+
},
|
|
73
|
+
toHaveProperty(key) {
|
|
74
|
+
const has = actual != null && Object.prototype.hasOwnProperty.call(actual, key);
|
|
75
|
+
check(has, `expected ${fmt(actual)} ${not}to have property "${key}"`);
|
|
76
|
+
},
|
|
77
|
+
toMatch(regex) {
|
|
78
|
+
check(regex.test(actual), `expected ${fmt(actual)} ${not}to match ${regex}`);
|
|
79
|
+
},
|
|
80
|
+
toBeInstanceOf(cls) {
|
|
81
|
+
check(actual instanceof cls, `expected value ${not}to be instance of ${cls.name}`);
|
|
82
|
+
},
|
|
83
|
+
toBeGreaterThan(n) {
|
|
84
|
+
check(actual > n, `expected ${fmt(actual)} ${not}to be > ${n}`);
|
|
85
|
+
},
|
|
86
|
+
toBeLessThan(n) {
|
|
87
|
+
check(actual < n, `expected ${fmt(actual)} ${not}to be < ${n}`);
|
|
88
|
+
},
|
|
89
|
+
async toThrow(matcher) {
|
|
90
|
+
let threw = false;
|
|
91
|
+
let thrown;
|
|
92
|
+
try {
|
|
93
|
+
if (typeof actual === 'function') {
|
|
94
|
+
await actual();
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
threw = true;
|
|
98
|
+
thrown = e;
|
|
99
|
+
}
|
|
100
|
+
if (!threw) {
|
|
101
|
+
return check(false, `expected function ${not}to throw`);
|
|
102
|
+
}
|
|
103
|
+
if (matcher instanceof RegExp) {
|
|
104
|
+
check(matcher.test(thrown.message), `expected thrown message ${not}to match ${matcher} (got: ${thrown.message})`);
|
|
105
|
+
} else if (typeof matcher === 'string') {
|
|
106
|
+
check(thrown.message.includes(matcher), `expected thrown message ${not}to contain "${matcher}" (got: ${thrown.message})`);
|
|
107
|
+
} else {
|
|
108
|
+
check(true, '');
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function expect(actual) {
|
|
115
|
+
const m = buildMatchers(actual, false);
|
|
116
|
+
m.not = buildMatchers(actual, true);
|
|
117
|
+
return m;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = expect;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// BXM fixture consumer — pretends to be a real BXM-based extension's background.
|
|
2
|
+
// Boot tests verify this SW comes up cleanly and exposes a couple of probe hooks.
|
|
3
|
+
|
|
4
|
+
globalThis.__bxmFixtureBooted = true;
|
|
5
|
+
globalThis.__bxmFixtureBootedAt = Date.now();
|
|
6
|
+
|
|
7
|
+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
8
|
+
if (msg && msg.type === 'fixture:hello') {
|
|
9
|
+
sendResponse({ ok: true, version: chrome.runtime.getManifest().version });
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
console.log('[bxm-fixture] background ready');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "BXM Fixture Consumer",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Used by BXM's boot-layer tests as a stand-in for a real consumer extension.",
|
|
6
|
+
"background": {
|
|
7
|
+
"service_worker": "background.js"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"default_popup": "popup.html",
|
|
11
|
+
"default_title": "BXM Fixture Consumer"
|
|
12
|
+
},
|
|
13
|
+
"options_ui": {
|
|
14
|
+
"page": "options.html",
|
|
15
|
+
"open_in_tab": true
|
|
16
|
+
},
|
|
17
|
+
"permissions": [
|
|
18
|
+
"storage"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>BXM Fixture Consumer — Popup</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1 id="main-content">Fixture Popup</h1>
|
|
9
|
+
<p>Boot tests assert that this page renders inside Chromium when loaded as an unpacked extension.</p>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// BXM test-harness background service worker.
|
|
2
|
+
//
|
|
3
|
+
// This SW does ONE job: stay alive long enough for the chromium runner to attach
|
|
4
|
+
// a CDP session and inject test code via Runtime.evaluate. The runner discovers
|
|
5
|
+
// this SW via Puppeteer's service-worker target API, then drives test execution
|
|
6
|
+
// directly from the parent Node process — this file is intentionally minimal.
|
|
7
|
+
//
|
|
8
|
+
// We set a couple of globals the injected test code can rely on:
|
|
9
|
+
// globalThis.BXM_TEST_MODE — picked up by Manager.isTesting()
|
|
10
|
+
// globalThis.__bxmTestEmit — defined by the runner before each test;
|
|
11
|
+
// used by injected code to report results
|
|
12
|
+
//
|
|
13
|
+
// We also publish a `chrome.runtime.onMessage` ping handler so view-layer tests
|
|
14
|
+
// (running in popup/options/sidepanel tabs) can verify the SW is alive.
|
|
15
|
+
|
|
16
|
+
globalThis.BXM_TEST_MODE = true;
|
|
17
|
+
|
|
18
|
+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
19
|
+
if (msg && msg.type === 'bxm:test:ping') {
|
|
20
|
+
sendResponse({ pong: true, ts: Date.now() });
|
|
21
|
+
return false; // sync response
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log('[bxm-harness] service worker ready');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "BXM Test Harness",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Loaded by the chromium runner to host background + view layer tests.",
|
|
6
|
+
"background": {
|
|
7
|
+
"service_worker": "background.js"
|
|
8
|
+
},
|
|
9
|
+
"action": {
|
|
10
|
+
"default_popup": "popup.html",
|
|
11
|
+
"default_title": "BXM Test Harness"
|
|
12
|
+
},
|
|
13
|
+
"options_ui": {
|
|
14
|
+
"page": "options.html",
|
|
15
|
+
"open_in_tab": true
|
|
16
|
+
},
|
|
17
|
+
"side_panel": {
|
|
18
|
+
"default_path": "sidepanel.html"
|
|
19
|
+
},
|
|
20
|
+
"permissions": [
|
|
21
|
+
"storage",
|
|
22
|
+
"sidePanel"
|
|
23
|
+
],
|
|
24
|
+
"host_permissions": [
|
|
25
|
+
"<all_urls>"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>BXM Test Harness — Options</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body data-bxm-context="options">
|
|
8
|
+
<h3 id="title">BXM Test Harness — Options</h3>
|
|
9
|
+
<p id="status">ready</p>
|
|
10
|
+
<pre id="log"></pre>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>BXM Test Harness — Side Panel</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body data-bxm-context="sidepanel">
|
|
8
|
+
<h3 id="title">BXM Test Harness — Side Panel</h3>
|
|
9
|
+
<p id="status">ready</p>
|
|
10
|
+
<pre id="log"></pre>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Public test API — what consumers see.
|
|
2
|
+
//
|
|
3
|
+
// Test files export a test definition. Three forms:
|
|
4
|
+
//
|
|
5
|
+
// Standalone:
|
|
6
|
+
// module.exports = {
|
|
7
|
+
// layer: 'build', // 'build' | 'background' | 'view' | 'boot'
|
|
8
|
+
// description: 'config has brand.id',
|
|
9
|
+
// timeout: 5000,
|
|
10
|
+
// run: async (ctx) => {
|
|
11
|
+
// const cfg = Manager.getConfig();
|
|
12
|
+
// ctx.expect(cfg.brand.id).toBeTruthy();
|
|
13
|
+
// },
|
|
14
|
+
// cleanup: async (ctx) => { ... },
|
|
15
|
+
// };
|
|
16
|
+
//
|
|
17
|
+
// Boot layer — spawns Chromium with the consumer's actual built `dist/` loaded as an
|
|
18
|
+
// unpacked extension and runs `inspect` against the live extension surface. Replaces
|
|
19
|
+
// shell-level smoke tests with deterministic, signal-driven pass/fail. Use this to
|
|
20
|
+
// verify the WHOLE integration: consumer scaffolds, brand config, real manifest, real boot.
|
|
21
|
+
//
|
|
22
|
+
// module.exports = {
|
|
23
|
+
// layer: 'boot',
|
|
24
|
+
// description: 'extension loads and SW boots',
|
|
25
|
+
// timeout: 20000,
|
|
26
|
+
// inspect: async ({ extension, page, expect, projectRoot }) => {
|
|
27
|
+
// expect(extension.id).toBeTruthy();
|
|
28
|
+
// expect(extension.manifest.manifest_version).toBe(3);
|
|
29
|
+
// },
|
|
30
|
+
// };
|
|
31
|
+
//
|
|
32
|
+
// Suite (sequential, shared state, stop on first failure):
|
|
33
|
+
// module.exports = {
|
|
34
|
+
// type: 'suite',
|
|
35
|
+
// layer: 'background',
|
|
36
|
+
// description: 'messaging round-trip',
|
|
37
|
+
// tests: [
|
|
38
|
+
// { name: 'send', run: async (ctx) => { ctx.state.echo = await chrome.runtime.sendMessage({ ping: 1 }); } },
|
|
39
|
+
// { name: 'reply', run: async (ctx) => { ctx.expect(ctx.state.echo.pong).toBe(1); } },
|
|
40
|
+
// ],
|
|
41
|
+
// };
|
|
42
|
+
//
|
|
43
|
+
// Group (sequential, shared state, runs ALL tests even if some fail):
|
|
44
|
+
// module.exports = {
|
|
45
|
+
// type: 'group',
|
|
46
|
+
// layer: 'build',
|
|
47
|
+
// tests: [ ... ],
|
|
48
|
+
// };
|
|
49
|
+
//
|
|
50
|
+
// Array form (treated as group):
|
|
51
|
+
// module.exports = [ { name, run }, ... ];
|
|
52
|
+
//
|
|
53
|
+
// The ctx (context) provided to every run/cleanup includes:
|
|
54
|
+
// - ctx.expect — Jest-compatible assertion library
|
|
55
|
+
// - ctx.state — shared object across tests in a suite/group
|
|
56
|
+
// - ctx.skip(reason) — throw to skip the current test at runtime
|
|
57
|
+
// - ctx.layer — current layer name
|
|
58
|
+
// - ctx.manager — BXM Manager instance (background / view layers only)
|
|
59
|
+
// - ctx.page — Puppeteer Page (view layer only)
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
expect: require('./assert.js'),
|
|
63
|
+
};
|