browser-commander 0.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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +296 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +20 -0
- package/.prettierignore +7 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +24 -0
- package/README.md +320 -0
- package/bunfig.toml +3 -0
- package/deno.json +7 -0
- package/eslint.config.js +125 -0
- package/examples/react-test-app/index.html +25 -0
- package/examples/react-test-app/package.json +19 -0
- package/examples/react-test-app/src/App.jsx +473 -0
- package/examples/react-test-app/src/main.jsx +10 -0
- package/examples/react-test-app/src/styles.css +323 -0
- package/examples/react-test-app/vite.config.js +9 -0
- package/package.json +89 -0
- package/scripts/changeset-version.mjs +38 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +86 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +216 -0
- package/scripts/instant-version-bump.mjs +121 -0
- package/scripts/merge-changesets.mjs +260 -0
- package/scripts/publish-to-npm.mjs +126 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +262 -0
- package/scripts/version-and-commit.mjs +237 -0
- package/src/ARCHITECTURE.md +270 -0
- package/src/README.md +517 -0
- package/src/bindings.js +298 -0
- package/src/browser/launcher.js +93 -0
- package/src/browser/navigation.js +513 -0
- package/src/core/constants.js +24 -0
- package/src/core/engine-adapter.js +466 -0
- package/src/core/engine-detection.js +49 -0
- package/src/core/logger.js +21 -0
- package/src/core/navigation-manager.js +503 -0
- package/src/core/navigation-safety.js +160 -0
- package/src/core/network-tracker.js +373 -0
- package/src/core/page-session.js +299 -0
- package/src/core/page-trigger-manager.js +564 -0
- package/src/core/preferences.js +46 -0
- package/src/elements/content.js +197 -0
- package/src/elements/locators.js +243 -0
- package/src/elements/selectors.js +360 -0
- package/src/elements/visibility.js +166 -0
- package/src/exports.js +121 -0
- package/src/factory.js +192 -0
- package/src/high-level/universal-logic.js +206 -0
- package/src/index.js +17 -0
- package/src/interactions/click.js +684 -0
- package/src/interactions/fill.js +383 -0
- package/src/interactions/scroll.js +341 -0
- package/src/utilities/url.js +33 -0
- package/src/utilities/wait.js +135 -0
- package/tests/e2e/playwright.e2e.test.js +442 -0
- package/tests/e2e/puppeteer.e2e.test.js +408 -0
- package/tests/helpers/mocks.js +542 -0
- package/tests/unit/bindings.test.js +218 -0
- package/tests/unit/browser/navigation.test.js +345 -0
- package/tests/unit/core/constants.test.js +72 -0
- package/tests/unit/core/engine-adapter.test.js +170 -0
- package/tests/unit/core/engine-detection.test.js +81 -0
- package/tests/unit/core/logger.test.js +80 -0
- package/tests/unit/core/navigation-safety.test.js +202 -0
- package/tests/unit/core/network-tracker.test.js +198 -0
- package/tests/unit/core/page-trigger-manager.test.js +358 -0
- package/tests/unit/elements/content.test.js +318 -0
- package/tests/unit/elements/locators.test.js +236 -0
- package/tests/unit/elements/selectors.test.js +302 -0
- package/tests/unit/elements/visibility.test.js +234 -0
- package/tests/unit/factory.test.js +174 -0
- package/tests/unit/high-level/universal-logic.test.js +299 -0
- package/tests/unit/interactions/click.test.js +340 -0
- package/tests/unit/interactions/fill.test.js +378 -0
- package/tests/unit/interactions/scroll.test.js +330 -0
- package/tests/unit/utilities/url.test.js +63 -0
- package/tests/unit/utilities/wait.test.js +207 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { createNetworkTracker } from '../../../src/core/network-tracker.js';
|
|
4
|
+
import {
|
|
5
|
+
createMockPlaywrightPage,
|
|
6
|
+
createMockLogger,
|
|
7
|
+
} from '../../helpers/mocks.js';
|
|
8
|
+
|
|
9
|
+
describe('network-tracker', () => {
|
|
10
|
+
let page;
|
|
11
|
+
let log;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
page = createMockPlaywrightPage();
|
|
15
|
+
log = createMockLogger();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('createNetworkTracker', () => {
|
|
19
|
+
it('should throw when page is not provided', () => {
|
|
20
|
+
assert.throws(
|
|
21
|
+
() => createNetworkTracker({ log, engine: 'playwright' }),
|
|
22
|
+
/page is required/
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should create network tracker', () => {
|
|
27
|
+
const tracker = createNetworkTracker({
|
|
28
|
+
page,
|
|
29
|
+
engine: 'playwright',
|
|
30
|
+
log,
|
|
31
|
+
});
|
|
32
|
+
assert.ok(tracker);
|
|
33
|
+
assert.ok(typeof tracker.startTracking === 'function');
|
|
34
|
+
assert.ok(typeof tracker.stopTracking === 'function');
|
|
35
|
+
assert.ok(typeof tracker.waitForNetworkIdle === 'function');
|
|
36
|
+
assert.ok(typeof tracker.getPendingCount === 'function');
|
|
37
|
+
assert.ok(typeof tracker.getPendingUrls === 'function');
|
|
38
|
+
assert.ok(typeof tracker.reset === 'function');
|
|
39
|
+
assert.ok(typeof tracker.on === 'function');
|
|
40
|
+
assert.ok(typeof tracker.off === 'function');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should start tracking without error', () => {
|
|
44
|
+
const tracker = createNetworkTracker({
|
|
45
|
+
page,
|
|
46
|
+
engine: 'playwright',
|
|
47
|
+
log,
|
|
48
|
+
});
|
|
49
|
+
tracker.startTracking();
|
|
50
|
+
// Should not throw
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should stop tracking without error', () => {
|
|
54
|
+
const tracker = createNetworkTracker({
|
|
55
|
+
page,
|
|
56
|
+
engine: 'playwright',
|
|
57
|
+
log,
|
|
58
|
+
});
|
|
59
|
+
tracker.startTracking();
|
|
60
|
+
tracker.stopTracking();
|
|
61
|
+
// Should not throw
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not start tracking twice', () => {
|
|
65
|
+
const tracker = createNetworkTracker({
|
|
66
|
+
page,
|
|
67
|
+
engine: 'playwright',
|
|
68
|
+
log,
|
|
69
|
+
});
|
|
70
|
+
tracker.startTracking();
|
|
71
|
+
tracker.startTracking(); // Should not throw or double-register
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should not stop tracking if not started', () => {
|
|
75
|
+
const tracker = createNetworkTracker({
|
|
76
|
+
page,
|
|
77
|
+
engine: 'playwright',
|
|
78
|
+
log,
|
|
79
|
+
});
|
|
80
|
+
tracker.stopTracking(); // Should not throw
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return 0 pending count initially', () => {
|
|
84
|
+
const tracker = createNetworkTracker({
|
|
85
|
+
page,
|
|
86
|
+
engine: 'playwright',
|
|
87
|
+
log,
|
|
88
|
+
});
|
|
89
|
+
assert.strictEqual(tracker.getPendingCount(), 0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return empty pending URLs initially', () => {
|
|
93
|
+
const tracker = createNetworkTracker({
|
|
94
|
+
page,
|
|
95
|
+
engine: 'playwright',
|
|
96
|
+
log,
|
|
97
|
+
});
|
|
98
|
+
const urls = tracker.getPendingUrls();
|
|
99
|
+
assert.ok(Array.isArray(urls));
|
|
100
|
+
assert.strictEqual(urls.length, 0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should reset without error', () => {
|
|
104
|
+
const tracker = createNetworkTracker({
|
|
105
|
+
page,
|
|
106
|
+
engine: 'playwright',
|
|
107
|
+
log,
|
|
108
|
+
});
|
|
109
|
+
tracker.reset();
|
|
110
|
+
// Should not throw
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should add event listener', () => {
|
|
114
|
+
const tracker = createNetworkTracker({
|
|
115
|
+
page,
|
|
116
|
+
engine: 'playwright',
|
|
117
|
+
log,
|
|
118
|
+
});
|
|
119
|
+
const callback = () => {};
|
|
120
|
+
tracker.on('onRequestStart', callback);
|
|
121
|
+
// Should not throw
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should remove event listener', () => {
|
|
125
|
+
const tracker = createNetworkTracker({
|
|
126
|
+
page,
|
|
127
|
+
engine: 'playwright',
|
|
128
|
+
log,
|
|
129
|
+
});
|
|
130
|
+
const callback = () => {};
|
|
131
|
+
tracker.on('onRequestStart', callback);
|
|
132
|
+
tracker.off('onRequestStart', callback);
|
|
133
|
+
// Should not throw
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle invalid event names gracefully', () => {
|
|
137
|
+
const tracker = createNetworkTracker({
|
|
138
|
+
page,
|
|
139
|
+
engine: 'playwright',
|
|
140
|
+
log,
|
|
141
|
+
});
|
|
142
|
+
const callback = () => {};
|
|
143
|
+
tracker.on('invalidEvent', callback);
|
|
144
|
+
tracker.off('invalidEvent', callback);
|
|
145
|
+
// Should not throw
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should accept custom idle timeout', () => {
|
|
149
|
+
const tracker = createNetworkTracker({
|
|
150
|
+
page,
|
|
151
|
+
engine: 'playwright',
|
|
152
|
+
log,
|
|
153
|
+
idleTimeout: 1000,
|
|
154
|
+
});
|
|
155
|
+
assert.ok(tracker);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should accept custom request timeout', () => {
|
|
159
|
+
const tracker = createNetworkTracker({
|
|
160
|
+
page,
|
|
161
|
+
engine: 'playwright',
|
|
162
|
+
log,
|
|
163
|
+
requestTimeout: 60000,
|
|
164
|
+
});
|
|
165
|
+
assert.ok(tracker);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('waitForNetworkIdle', () => {
|
|
170
|
+
it('should resolve immediately when no pending requests', async () => {
|
|
171
|
+
const tracker = createNetworkTracker({
|
|
172
|
+
page,
|
|
173
|
+
engine: 'playwright',
|
|
174
|
+
log,
|
|
175
|
+
idleTimeout: 10, // Very short for testing
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await tracker.waitForNetworkIdle({
|
|
179
|
+
timeout: 100,
|
|
180
|
+
idleTime: 10,
|
|
181
|
+
});
|
|
182
|
+
assert.ok(result === true || result === false); // May timeout in fast test
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should accept timeout option', async () => {
|
|
186
|
+
const tracker = createNetworkTracker({
|
|
187
|
+
page,
|
|
188
|
+
engine: 'playwright',
|
|
189
|
+
log,
|
|
190
|
+
idleTimeout: 10,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await tracker.waitForNetworkIdle({ timeout: 50 });
|
|
194
|
+
// Should return within timeout
|
|
195
|
+
assert.ok(typeof result === 'boolean');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
ActionStoppedError,
|
|
5
|
+
isActionStoppedError,
|
|
6
|
+
makeUrlCondition,
|
|
7
|
+
allConditions,
|
|
8
|
+
anyCondition,
|
|
9
|
+
notCondition,
|
|
10
|
+
createPageTriggerManager,
|
|
11
|
+
} from '../../../src/core/page-trigger-manager.js';
|
|
12
|
+
import {
|
|
13
|
+
createMockNavigationManager,
|
|
14
|
+
createMockLogger,
|
|
15
|
+
} from '../../helpers/mocks.js';
|
|
16
|
+
|
|
17
|
+
describe('page-trigger-manager', () => {
|
|
18
|
+
describe('ActionStoppedError', () => {
|
|
19
|
+
it('should create error with default message', () => {
|
|
20
|
+
const error = new ActionStoppedError();
|
|
21
|
+
assert.strictEqual(error.name, 'ActionStoppedError');
|
|
22
|
+
assert.strictEqual(error.message, 'Action stopped due to navigation');
|
|
23
|
+
assert.strictEqual(error.isActionStopped, true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should create error with custom message', () => {
|
|
27
|
+
const error = new ActionStoppedError('Custom message');
|
|
28
|
+
assert.strictEqual(error.message, 'Custom message');
|
|
29
|
+
assert.strictEqual(error.isActionStopped, true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be instance of Error', () => {
|
|
33
|
+
const error = new ActionStoppedError();
|
|
34
|
+
assert.ok(error instanceof Error);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('isActionStoppedError', () => {
|
|
39
|
+
it('should return true for ActionStoppedError', () => {
|
|
40
|
+
const error = new ActionStoppedError();
|
|
41
|
+
assert.strictEqual(isActionStoppedError(error), true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return true for error with isActionStopped flag', () => {
|
|
45
|
+
const error = new Error('Some error');
|
|
46
|
+
error.isActionStopped = true;
|
|
47
|
+
assert.strictEqual(isActionStoppedError(error), true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return true for error with ActionStoppedError name', () => {
|
|
51
|
+
const error = new Error('Some error');
|
|
52
|
+
error.name = 'ActionStoppedError';
|
|
53
|
+
assert.strictEqual(isActionStoppedError(error), true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return false for regular error', () => {
|
|
57
|
+
const error = new Error('Regular error');
|
|
58
|
+
assert.strictEqual(isActionStoppedError(error), false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return falsy for null', () => {
|
|
62
|
+
assert.ok(!isActionStoppedError(null));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return falsy for undefined', () => {
|
|
66
|
+
assert.ok(!isActionStoppedError(undefined));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('makeUrlCondition', () => {
|
|
71
|
+
describe('function patterns', () => {
|
|
72
|
+
it('should wrap function pattern', () => {
|
|
73
|
+
const condition = makeUrlCondition((url) => url.includes('test'));
|
|
74
|
+
assert.strictEqual(condition({ url: 'https://test.com' }), true);
|
|
75
|
+
assert.strictEqual(condition({ url: 'https://example.com' }), false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should pass context to function', () => {
|
|
79
|
+
const condition = makeUrlCondition(
|
|
80
|
+
(url, ctx) => ctx.someValue === true
|
|
81
|
+
);
|
|
82
|
+
assert.strictEqual(
|
|
83
|
+
condition({ url: 'https://test.com', someValue: true }),
|
|
84
|
+
true
|
|
85
|
+
);
|
|
86
|
+
assert.strictEqual(
|
|
87
|
+
condition({ url: 'https://test.com', someValue: false }),
|
|
88
|
+
false
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('RegExp patterns', () => {
|
|
94
|
+
it('should match RegExp pattern', () => {
|
|
95
|
+
const condition = makeUrlCondition(/\/product\/\d+/);
|
|
96
|
+
assert.strictEqual(
|
|
97
|
+
condition({ url: 'https://example.com/product/123' }),
|
|
98
|
+
true
|
|
99
|
+
);
|
|
100
|
+
assert.strictEqual(
|
|
101
|
+
condition({ url: 'https://example.com/product/' }),
|
|
102
|
+
false
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('string patterns', () => {
|
|
108
|
+
it('should match exact URL', () => {
|
|
109
|
+
const condition = makeUrlCondition('https://example.com/page');
|
|
110
|
+
assert.strictEqual(
|
|
111
|
+
condition({ url: 'https://example.com/page' }),
|
|
112
|
+
true
|
|
113
|
+
);
|
|
114
|
+
assert.strictEqual(
|
|
115
|
+
condition({ url: 'https://example.com/page?foo=bar' }),
|
|
116
|
+
true
|
|
117
|
+
);
|
|
118
|
+
assert.strictEqual(
|
|
119
|
+
condition({ url: 'https://example.com/other' }),
|
|
120
|
+
false
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should match *substring* pattern (contains)', () => {
|
|
125
|
+
const condition = makeUrlCondition('*checkout*');
|
|
126
|
+
assert.strictEqual(
|
|
127
|
+
condition({ url: 'https://example.com/checkout/step1' }),
|
|
128
|
+
true
|
|
129
|
+
);
|
|
130
|
+
assert.strictEqual(
|
|
131
|
+
condition({ url: 'https://checkout.example.com' }),
|
|
132
|
+
true
|
|
133
|
+
);
|
|
134
|
+
assert.strictEqual(
|
|
135
|
+
condition({ url: 'https://example.com/cart' }),
|
|
136
|
+
false
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should match *suffix pattern (ends with)', () => {
|
|
141
|
+
const condition = makeUrlCondition('*.json');
|
|
142
|
+
assert.strictEqual(
|
|
143
|
+
condition({ url: 'https://api.example.com/data.json' }),
|
|
144
|
+
true
|
|
145
|
+
);
|
|
146
|
+
assert.strictEqual(
|
|
147
|
+
condition({ url: 'https://api.example.com/data.xml' }),
|
|
148
|
+
false
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should match prefix* pattern (starts with)', () => {
|
|
153
|
+
const condition = makeUrlCondition('/api/*');
|
|
154
|
+
assert.strictEqual(condition({ url: '/api/users' }), true);
|
|
155
|
+
assert.strictEqual(condition({ url: '/api/products' }), true);
|
|
156
|
+
assert.strictEqual(condition({ url: '/web/page' }), false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should match express-style :param patterns', () => {
|
|
160
|
+
const condition = makeUrlCondition('/vacancy/:id');
|
|
161
|
+
assert.strictEqual(condition({ url: '/vacancy/123' }), true);
|
|
162
|
+
assert.strictEqual(condition({ url: '/vacancy/abc' }), true);
|
|
163
|
+
assert.strictEqual(condition({ url: '/vacancy/' }), false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should match express-style patterns with multiple params', () => {
|
|
167
|
+
const condition = makeUrlCondition('/user/:userId/profile');
|
|
168
|
+
assert.strictEqual(condition({ url: '/user/123/profile' }), true);
|
|
169
|
+
assert.strictEqual(condition({ url: '/user/abc/profile' }), true); // Params match any non-slash chars
|
|
170
|
+
assert.strictEqual(condition({ url: '/user//profile' }), false); // Empty param doesn't match
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should match URL containing path (no wildcards, no params)', () => {
|
|
174
|
+
const condition = makeUrlCondition('/admin');
|
|
175
|
+
assert.strictEqual(
|
|
176
|
+
condition({ url: 'https://example.com/admin/dashboard' }),
|
|
177
|
+
true
|
|
178
|
+
);
|
|
179
|
+
assert.strictEqual(
|
|
180
|
+
condition({ url: 'https://example.com/user' }),
|
|
181
|
+
false
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('invalid patterns', () => {
|
|
187
|
+
it('should throw for invalid pattern type', () => {
|
|
188
|
+
assert.throws(() => makeUrlCondition(123), /Invalid URL pattern type/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should throw for null pattern', () => {
|
|
192
|
+
assert.throws(() => makeUrlCondition(null), /Invalid URL pattern type/);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('condition combinators', () => {
|
|
198
|
+
describe('allConditions', () => {
|
|
199
|
+
it('should return true when all conditions match', () => {
|
|
200
|
+
const condition = allConditions(
|
|
201
|
+
makeUrlCondition('*example.com*'),
|
|
202
|
+
makeUrlCondition('*checkout*')
|
|
203
|
+
);
|
|
204
|
+
assert.strictEqual(
|
|
205
|
+
condition({ url: 'https://example.com/checkout' }),
|
|
206
|
+
true
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return false when any condition fails', () => {
|
|
211
|
+
const condition = allConditions(
|
|
212
|
+
makeUrlCondition('*example.com*'),
|
|
213
|
+
makeUrlCondition('*checkout*')
|
|
214
|
+
);
|
|
215
|
+
assert.strictEqual(
|
|
216
|
+
condition({ url: 'https://example.com/cart' }),
|
|
217
|
+
false
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('anyCondition', () => {
|
|
223
|
+
it('should return true when any condition matches', () => {
|
|
224
|
+
const condition = anyCondition(
|
|
225
|
+
makeUrlCondition('*cart*'),
|
|
226
|
+
makeUrlCondition('*checkout*')
|
|
227
|
+
);
|
|
228
|
+
assert.strictEqual(
|
|
229
|
+
condition({ url: 'https://example.com/cart' }),
|
|
230
|
+
true
|
|
231
|
+
);
|
|
232
|
+
assert.strictEqual(
|
|
233
|
+
condition({ url: 'https://example.com/checkout' }),
|
|
234
|
+
true
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return false when no conditions match', () => {
|
|
239
|
+
const condition = anyCondition(
|
|
240
|
+
makeUrlCondition('*cart*'),
|
|
241
|
+
makeUrlCondition('*checkout*')
|
|
242
|
+
);
|
|
243
|
+
assert.strictEqual(
|
|
244
|
+
condition({ url: 'https://example.com/home' }),
|
|
245
|
+
false
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('notCondition', () => {
|
|
251
|
+
it('should negate condition', () => {
|
|
252
|
+
const condition = notCondition(makeUrlCondition('*admin*'));
|
|
253
|
+
assert.strictEqual(
|
|
254
|
+
condition({ url: 'https://example.com/user' }),
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
assert.strictEqual(
|
|
258
|
+
condition({ url: 'https://example.com/admin' }),
|
|
259
|
+
false
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('createPageTriggerManager', () => {
|
|
266
|
+
let navigationManager;
|
|
267
|
+
let log;
|
|
268
|
+
|
|
269
|
+
beforeEach(() => {
|
|
270
|
+
navigationManager = createMockNavigationManager();
|
|
271
|
+
log = createMockLogger();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should throw error when navigationManager is not provided', () => {
|
|
275
|
+
assert.throws(
|
|
276
|
+
() => createPageTriggerManager({ log }),
|
|
277
|
+
/navigationManager is required/
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should create page trigger manager', () => {
|
|
282
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
283
|
+
assert.ok(manager);
|
|
284
|
+
assert.ok(typeof manager.pageTrigger === 'function');
|
|
285
|
+
assert.ok(typeof manager.stopCurrentAction === 'function');
|
|
286
|
+
assert.ok(typeof manager.isRunning === 'function');
|
|
287
|
+
assert.ok(typeof manager.destroy === 'function');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should register a trigger', () => {
|
|
291
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
292
|
+
|
|
293
|
+
const unregister = manager.pageTrigger({
|
|
294
|
+
name: 'test-trigger',
|
|
295
|
+
condition: () => true,
|
|
296
|
+
action: async () => {},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
assert.ok(typeof unregister === 'function');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should throw when condition is not a function', () => {
|
|
303
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
304
|
+
|
|
305
|
+
assert.throws(
|
|
306
|
+
() =>
|
|
307
|
+
manager.pageTrigger({
|
|
308
|
+
name: 'test',
|
|
309
|
+
condition: 'not-a-function',
|
|
310
|
+
action: async () => {},
|
|
311
|
+
}),
|
|
312
|
+
/condition must be a function/
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should throw when action is not a function', () => {
|
|
317
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
318
|
+
|
|
319
|
+
assert.throws(
|
|
320
|
+
() =>
|
|
321
|
+
manager.pageTrigger({
|
|
322
|
+
name: 'test',
|
|
323
|
+
condition: () => true,
|
|
324
|
+
action: 'not-a-function',
|
|
325
|
+
}),
|
|
326
|
+
/action must be a function/
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should unregister trigger', () => {
|
|
331
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
332
|
+
|
|
333
|
+
const unregister = manager.pageTrigger({
|
|
334
|
+
name: 'test-trigger',
|
|
335
|
+
condition: () => true,
|
|
336
|
+
action: async () => {},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Should not throw
|
|
340
|
+
unregister();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should report not running initially', () => {
|
|
344
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
345
|
+
assert.strictEqual(manager.isRunning(), false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should return null for getCurrentTriggerName when no action running', () => {
|
|
349
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
350
|
+
assert.strictEqual(manager.getCurrentTriggerName(), null);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should destroy without error', async () => {
|
|
354
|
+
const manager = createPageTriggerManager({ navigationManager, log });
|
|
355
|
+
await manager.destroy();
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|