browser-extension-manager 1.3.48 → 1.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/README.md +109 -70
- package/dist/background.js +4 -0
- package/dist/build.js +3 -0
- package/dist/cli.js +1 -0
- package/dist/commands/test.js +55 -0
- package/dist/content.js +4 -0
- package/dist/defaults/CLAUDE.md +66 -6
- package/dist/gulp/tasks/defaults.js +20 -4
- package/dist/gulp/tasks/distribute.js +36 -33
- package/dist/gulp/tasks/html.js +82 -79
- package/dist/gulp/tasks/utils/template-transform.js +35 -35
- 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 +407 -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 +162 -0
- package/dist/test/suites/build/mode-helpers.test.js +96 -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 +92 -0
- package/package.json +16 -13
package/dist/gulp/tasks/html.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const Manager = new (require('../../build.js'));
|
|
3
3
|
const logger = Manager.logger('html');
|
|
4
4
|
const { src, dest, watch, series } = require('gulp');
|
|
5
|
-
const
|
|
5
|
+
const { Transform } = require('node:stream');
|
|
6
6
|
const jetpack = require('fs-jetpack');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { template } = require('node-powertools');
|
|
@@ -51,86 +51,89 @@ function html(complete) {
|
|
|
51
51
|
|
|
52
52
|
// Process HTML transform
|
|
53
53
|
function processHtml(templateContent) {
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
// Get the view name from the file path
|
|
62
|
-
const viewName = path.basename(file.path, '.html');
|
|
63
|
-
const relativePath = path.relative(path.join(rootPathProject, 'src/views'), file.dirname);
|
|
64
|
-
const viewNameWithPath = relativePath ? `${relativePath}/${viewName}` : viewName;
|
|
65
|
-
|
|
66
|
-
// Read the content (body HTML)
|
|
67
|
-
const bodyContent = file.contents.toString();
|
|
68
|
-
|
|
69
|
-
// Determine the component name for CSS/JS loading
|
|
70
|
-
// Template already includes /components/ prefix, so just provide the component path
|
|
71
|
-
// Pages can have multiple files (index, pricing, login, etc.)
|
|
72
|
-
// Other components (popup, options, etc.) only have index.html
|
|
73
|
-
let componentName;
|
|
74
|
-
if (relativePath.startsWith('pages/') || relativePath === 'pages') {
|
|
75
|
-
// Pages directory: include full path with filename
|
|
76
|
-
// pages/index.html -> pages/index
|
|
77
|
-
// pages/pricing.html -> pages/pricing
|
|
78
|
-
componentName = viewNameWithPath;
|
|
79
|
-
} else if (relativePath && viewName === 'index') {
|
|
80
|
-
// Other components with index.html: use just the directory name
|
|
81
|
-
// popup/index.html -> popup
|
|
82
|
-
// options/index.html -> options
|
|
83
|
-
componentName = relativePath;
|
|
84
|
-
} else if (relativePath) {
|
|
85
|
-
// Non-index files in other directories: use full path
|
|
86
|
-
componentName = viewNameWithPath;
|
|
87
|
-
} else {
|
|
88
|
-
// No path: use viewName
|
|
89
|
-
componentName = viewName;
|
|
54
|
+
return new Transform({
|
|
55
|
+
objectMode: true,
|
|
56
|
+
transform(file, _, callback) {
|
|
57
|
+
// Skip if it's a directory
|
|
58
|
+
if (file.isDirectory()) {
|
|
59
|
+
return callback(null, file);
|
|
90
60
|
}
|
|
91
61
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
62
|
+
try {
|
|
63
|
+
// Get the view name from the file path
|
|
64
|
+
const viewName = path.basename(file.path, '.html');
|
|
65
|
+
const relativePath = path.relative(path.join(rootPathProject, 'src/views'), file.dirname);
|
|
66
|
+
const viewNameWithPath = relativePath ? `${relativePath}/${viewName}` : viewName;
|
|
67
|
+
|
|
68
|
+
// Read the content (body HTML)
|
|
69
|
+
const bodyContent = file.contents.toString();
|
|
70
|
+
|
|
71
|
+
// Determine the component name for CSS/JS loading
|
|
72
|
+
// Template already includes /components/ prefix, so just provide the component path
|
|
73
|
+
// Pages can have multiple files (index, pricing, login, etc.)
|
|
74
|
+
// Other components (popup, options, etc.) only have index.html
|
|
75
|
+
let componentName;
|
|
76
|
+
if (relativePath.startsWith('pages/') || relativePath === 'pages') {
|
|
77
|
+
// Pages directory: include full path with filename
|
|
78
|
+
// pages/index.html -> pages/index
|
|
79
|
+
// pages/pricing.html -> pages/pricing
|
|
80
|
+
componentName = viewNameWithPath;
|
|
81
|
+
} else if (relativePath && viewName === 'index') {
|
|
82
|
+
// Other components with index.html: use just the directory name
|
|
83
|
+
// popup/index.html -> popup
|
|
84
|
+
// options/index.html -> options
|
|
85
|
+
componentName = relativePath;
|
|
86
|
+
} else if (relativePath) {
|
|
87
|
+
// Non-index files in other directories: use full path
|
|
88
|
+
componentName = viewNameWithPath;
|
|
89
|
+
} else {
|
|
90
|
+
// No path: use viewName
|
|
91
|
+
componentName = viewName;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Prepare template data
|
|
95
|
+
const data = {
|
|
96
|
+
content: bodyContent,
|
|
97
|
+
page: {
|
|
98
|
+
name: componentName,
|
|
99
|
+
path: viewNameWithPath,
|
|
100
|
+
title: config.brand?.name || 'Extension',
|
|
101
|
+
},
|
|
102
|
+
theme: {
|
|
103
|
+
appearance: config.theme?.appearance || 'dark',
|
|
104
|
+
},
|
|
105
|
+
brand: config.brand || {},
|
|
106
|
+
cacheBust: Date.now(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Apply template with custom brackets
|
|
110
|
+
// First, template the body content to replace any {{ }} placeholders in the view
|
|
111
|
+
const templatedBody = template(bodyContent, data, {
|
|
112
|
+
brackets: ['{{', '}}'],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Update data with templated body
|
|
116
|
+
data.content = templatedBody;
|
|
117
|
+
|
|
118
|
+
// Then template the outer page template
|
|
119
|
+
const rendered = template(templateContent, data, {
|
|
120
|
+
brackets: ['{{', '}}'],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Update file contents
|
|
124
|
+
file.contents = Buffer.from(rendered);
|
|
125
|
+
|
|
126
|
+
// Log
|
|
127
|
+
logger.log(`Processed: ${viewNameWithPath}.html`);
|
|
128
|
+
|
|
129
|
+
// Push the file
|
|
130
|
+
this.push(file);
|
|
131
|
+
return callback();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error('Error processing HTML:', error);
|
|
134
|
+
return callback(error);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
134
137
|
});
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
// Libraries
|
|
2
|
-
const
|
|
2
|
+
const { Transform } = require('node:stream');
|
|
3
3
|
const { template } = require('node-powertools');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Creates a
|
|
7
|
+
* Creates a transform stream that processes template variables in files
|
|
8
8
|
**/
|
|
9
9
|
function createTemplateTransform(data) {
|
|
10
10
|
const extensions = ['html', 'md', 'liquid', 'json', 'yml', 'yaml']
|
|
11
11
|
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// Check if file extension matches
|
|
19
|
-
const ext = path.extname(file.path).toLowerCase().slice(1);
|
|
20
|
-
if (!extensions.includes(ext)) {
|
|
21
|
-
return callback(null, file);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Log
|
|
25
|
-
// console.log(`Processing file: ${file.path}`);
|
|
26
|
-
|
|
27
|
-
// Process the file contents
|
|
28
|
-
try {
|
|
29
|
-
const contents = file.contents.toString();
|
|
30
|
-
|
|
31
|
-
// Process templates
|
|
32
|
-
const templated = template(contents, data, {
|
|
33
|
-
brackets: ['[', ']'],
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Update file contents if changed
|
|
37
|
-
if (contents !== templated) {
|
|
38
|
-
file.contents = Buffer.from(templated);
|
|
39
|
-
const relativePath = file.relative || file.path;
|
|
12
|
+
return new Transform({
|
|
13
|
+
objectMode: true,
|
|
14
|
+
transform(file, encoding, callback) {
|
|
15
|
+
// Skip directories
|
|
16
|
+
if (file.isDirectory()) {
|
|
17
|
+
return callback(null, file);
|
|
40
18
|
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(`Error processing templates in ${file.path}:`, error);
|
|
43
|
-
}
|
|
44
19
|
|
|
45
|
-
|
|
46
|
-
|
|
20
|
+
// Check if file extension matches
|
|
21
|
+
const ext = path.extname(file.path).toLowerCase().slice(1);
|
|
22
|
+
if (!extensions.includes(ext)) {
|
|
23
|
+
return callback(null, file);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Process the file contents
|
|
27
|
+
try {
|
|
28
|
+
const contents = file.contents.toString();
|
|
29
|
+
|
|
30
|
+
// Process templates
|
|
31
|
+
const templated = template(contents, data, {
|
|
32
|
+
brackets: ['[', ']'],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Update file contents if changed
|
|
36
|
+
if (contents !== templated) {
|
|
37
|
+
file.contents = Buffer.from(templated);
|
|
38
|
+
const relativePath = file.relative || file.path;
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`Error processing templates in ${file.path}:`, error);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Pass the file through
|
|
45
|
+
callback(null, file);
|
|
46
|
+
},
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
|
package/dist/offscreen.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Libraries
|
|
2
2
|
import extension from './lib/extension.js';
|
|
3
3
|
import LoggerLite from './lib/logger-lite.js';
|
|
4
|
+
import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
|
|
4
5
|
|
|
5
6
|
// Class
|
|
6
7
|
class Manager {
|
|
@@ -23,5 +24,8 @@ class Manager {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
// Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
|
|
28
|
+
attachModeHelpers(Manager);
|
|
29
|
+
|
|
26
30
|
// Export
|
|
27
31
|
export default Manager;
|
package/dist/options.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/page.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/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>
|