Files
memind/mindspace-rybbit.test.mjs
john c7718c683b
Memind CI / Test, build, and release guards (push) Successful in 1m36s
fix: inject Rybbit tracking on m.tkmind.cn publication pages
Publication routes and share shells were missing the Rybbit tracker, and
bootstrap now reuses the stored MindSpace analytics secret when Rybbit env
secret is absent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:10:07 +08:00

116 lines
3.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import vm from 'node:vm';
import {
injectMindSpaceRybbit,
resolveMindSpaceRybbitConfig,
} from './mindspace-rybbit.mjs';
test('rybbit config is disabled unless explicitly enabled and configured', () => {
assert.equal(resolveMindSpaceRybbitConfig({ MEMIND_RYBBIT_ENABLED: 'true' }).enabled, false);
assert.equal(resolveMindSpaceRybbitConfig({
MEMIND_RYBBIT_ENABLED: 'true',
MEMIND_RYBBIT_SITE_ID: '2',
}).enabled, false);
const config = resolveMindSpaceRybbitConfig({
MEMIND_RYBBIT_ENABLED: 'true',
MEMIND_RYBBIT_SITE_ID: '2',
MEMIND_RYBBIT_ID_SECRET: 'local-secret',
MEMIND_RYBBIT_URL: 'https://rybbit.tkmind.cn',
});
assert.equal(config.enabled, true);
assert.equal(config.siteId, '2');
assert.equal(config.rybbitUrl, 'https://rybbit.tkmind.cn');
assert.equal(config.hostPath, '/rybbit');
assert.equal(config.scriptPath, '/rybbit/script.js');
});
test('rybbit config can reuse stored analytics secret', () => {
const config = resolveMindSpaceRybbitConfig({
MEMIND_RYBBIT_ENABLED: 'true',
MEMIND_RYBBIT_SITE_ID: '2',
}, {
idSecret: 'stored-secret',
});
assert.equal(config.enabled, true);
assert.equal(config.idSecret, 'stored-secret');
});
test('injects one local same-origin rybbit tracker with page dimensions', () => {
const html = '<!doctype html><html><head><title>Demo</title></head><body><h1>Demo</h1></body></html>';
const out = injectMindSpaceRybbit(html, {
ownerId: 'user-123',
ownerLabel: '张三',
pageId: 'page-1',
publicationId: 'pub-1',
config: {
enabled: true,
siteId: '2',
idSecret: 'secret',
scriptPath: '/rybbit/script.js',
hostPath: '/rybbit',
},
});
assert.match(out, /src="\/rybbit\/script\.js"/);
assert.match(out, /data-site-id="2"/);
assert.match(out, /data-memind-rybbit="1"/);
assert.match(out, /r\.identify\(d\.owner_id,/);
assert.match(out, /r\.event\(n,p\)/);
assert.match(out, /page_click/);
assert.match(out, /page_form_submit/);
assert.match(out, /page_scroll_/);
assert.match(out, /page_engaged_10s/);
assert.match(out, /"username":"张三"/);
assert.doesNotMatch(out, /user-123/);
assert.equal(injectMindSpaceRybbit(out, {
ownerId: 'user-123',
config: { enabled: true, siteId: '2', idSecret: 'secret' },
}), out);
});
test('identifies the pseudonymous owner once rybbit is ready', () => {
const out = injectMindSpaceRybbit('<!doctype html><html><head></head><body></body></html>', {
ownerId: 'user-123',
ownerSegment: 'plan:pro',
ownerLabel: '张三',
channel: 'h5',
config: {
enabled: true,
siteId: '2',
idSecret: 'secret',
scriptPath: '/rybbit/script.js',
hostPath: '/rybbit',
},
});
const inlineScript = out.match(/<script data-memind-rybbit="1">([\s\S]*?)<\/script>/)?.[1];
assert.ok(inlineScript);
const calls = [];
const timers = [];
vm.runInNewContext(inlineScript, {
window: {
rybbit: {
identify: (...args) => calls.push(['identify', ...args]),
event: (...args) => calls.push(['event', ...args]),
},
innerHeight: 800,
scrollY: 0,
addEventListener: () => {},
},
document: {
readyState: 'complete',
title: 'Demo',
documentElement: { scrollHeight: 1600 },
addEventListener: () => {},
},
location: { href: 'http://127.0.0.1:8081/MindSpace/demo/public/page.html' },
setTimeout: (fn) => { timers.push(fn); },
});
assert.equal(calls[0]?.[0], 'identify');
assert.match(String(calls[0]?.[1] ?? ''), /^[a-f0-9]{32}$/);
assert.equal(calls[0]?.[2]?.username, '张三');
assert.equal(calls[0]?.[2]?.owner_segment, 'plan:pro');
});