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.
Files changed (49) hide show
  1. package/README.md +112 -70
  2. package/dist/background.js +6 -1
  3. package/dist/build.js +5 -7
  4. package/dist/cli.js +1 -0
  5. package/dist/commands/install.js +1 -1
  6. package/dist/commands/test.js +55 -0
  7. package/dist/content.js +4 -0
  8. package/dist/defaults/CHANGELOG.md +15 -0
  9. package/dist/defaults/CLAUDE.md +82 -6
  10. package/dist/defaults/docs/README.md +17 -0
  11. package/dist/defaults/test/README.md +32 -0
  12. package/dist/defaults/test/_init.js +10 -0
  13. package/dist/gulp/tasks/defaults.js +15 -2
  14. package/dist/offscreen.js +4 -0
  15. package/dist/options.js +4 -0
  16. package/dist/page.js +4 -0
  17. package/dist/popup.js +4 -0
  18. package/dist/sidepanel.js +4 -0
  19. package/dist/test/assert.js +120 -0
  20. package/dist/test/fixtures/consumer-extension/dist/background.js +15 -0
  21. package/dist/test/fixtures/consumer-extension/dist/manifest.json +20 -0
  22. package/dist/test/fixtures/consumer-extension/dist/options.html +10 -0
  23. package/dist/test/fixtures/consumer-extension/dist/popup.html +11 -0
  24. package/dist/test/harness/extension/background.js +26 -0
  25. package/dist/test/harness/extension/manifest.json +27 -0
  26. package/dist/test/harness/extension/options.html +12 -0
  27. package/dist/test/harness/extension/popup.html +12 -0
  28. package/dist/test/harness/extension/sidepanel.html +12 -0
  29. package/dist/test/index.js +63 -0
  30. package/dist/test/runner.js +469 -0
  31. package/dist/test/runners/boot.js +201 -0
  32. package/dist/test/runners/chromium.js +399 -0
  33. package/dist/test/suites/background/messaging.test.js +42 -0
  34. package/dist/test/suites/background/storage.test.js +44 -0
  35. package/dist/test/suites/background/sw-context.test.js +47 -0
  36. package/dist/test/suites/boot/extension-loads.test.js +56 -0
  37. package/dist/test/suites/build/affiliatizer.test.js +63 -0
  38. package/dist/test/suites/build/cli.test.js +123 -0
  39. package/dist/test/suites/build/expect.test.js +47 -0
  40. package/dist/test/suites/build/exports.test.js +52 -0
  41. package/dist/test/suites/build/extension-fallback.test.js +41 -0
  42. package/dist/test/suites/build/logger-lite.test.js +59 -0
  43. package/dist/test/suites/build/manager.test.js +184 -0
  44. package/dist/test/suites/build/mode-helpers.test.js +135 -0
  45. package/dist/test/suites/view/options-and-sidepanel.test.js +14 -0
  46. package/dist/test/suites/view/popup-context.test.js +51 -0
  47. package/dist/test/suites/view/sidepanel.test.js +14 -0
  48. package/dist/utils/mode-helpers.js +117 -0
  49. 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,10 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>BXM Fixture Consumer — Options</title>
6
+ </head>
7
+ <body>
8
+ <h1>Fixture Options</h1>
9
+ </body>
10
+ </html>
@@ -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 — Popup</title>
6
+ </head>
7
+ <body data-bxm-context="popup">
8
+ <h3 id="title">BXM Test Harness</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
+ };