Files
Haven/test/groupDmClient.test.js
T

105 lines
4.7 KiB
JavaScript

'use strict';
// What the app does when a group DM goes away for this user (#5740): a group
// missing from a fresh channel list is dropped (a 1:1 DM the server left out
// is still kept, as before), a kick from a group says so, and the group
// closes in the pop-out DM window as well.
//
// node --test test/groupDmClient.test.js
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const ROOT = path.join(__dirname, '..');
const FILES = ['app-socket.js', 'app-socket-channels.js', 'app-socket-events.js'];
const en = JSON.parse(fs.readFileSync(path.join(ROOT, 'public/locales/en.json'), 'utf8'));
const lookup = (key) => key.split('.').reduce((o, k) => (o ? o[k] : undefined), en);
const t = (key, vars = {}) => String(lookup(key) || key).replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k]);
function storage() {
const data = new Map();
return { getItem: (k) => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)), removeItem: (k) => data.delete(k) };
}
function loadMethods() {
const methods = {};
for (const name of FILES) {
const src = fs.readFileSync(path.join(ROOT, 'public/js/modules', name), 'utf8');
const context = vm.createContext({
module: { exports: {} }, t, URLSearchParams, console, setTimeout, clearTimeout,
localStorage: storage(), sessionStorage: storage(),
window: { location: { search: '', pathname: '/app' }, history: { replaceState() {} } },
document: { getElementById: () => null, querySelector: () => null, querySelectorAll: () => [] },
});
vm.runInContext(src.replace(/^export default/m, 'module.exports ='), context, { filename: name });
Object.assign(methods, context.module.exports);
}
return methods;
}
/** An app with just the socket listeners, everything else a no-op. */
function makeApp(fields) {
const handlers = {};
const toasts = [];
const base = {
...loadMethods(),
socket: { on: (ev, h) => { handlers[ev] = h; }, emit() {}, off() {} },
unreadCounts: {},
toasts,
_showToast: (message, type) => toasts.push({ message, type }),
_groupName: (ch) => ch.name,
...fields,
};
const app = new Proxy(base, { get: (o, k) => (k in o ? o[k] : (typeof k === 'string' && k.startsWith('_') ? () => {} : undefined)) });
base._listenChannelsAndMessages.call(app);
base._listenPresenceAndVoice.call(app);
base._listenAdminAndPrefs.call(app);
return { app, handlers, toasts };
}
const lounge = { id: 1, code: 'aaaaaaaa', name: 'lounge', is_dm: 0 };
const group = { id: 2, code: 'bbbbbbbb', name: 'Crew', is_dm: 1, is_group: 1 };
const oneToOne = { id: 3, code: 'cccccccc', name: 'DM', is_dm: 1, dm_target: { id: 9, username: 'zed' } };
test('a group missing from a fresh channel list is dropped, a 1:1 DM is kept', () => {
const { app, handlers } = makeApp({ channels: [lounge, group, oneToOne] });
handlers['channels-list']([lounge]);
assert.deepEqual(app.channels.map((c) => c.code), [lounge.code, oneToOne.code]);
});
test('a group still in the list stays', () => {
const { app, handlers } = makeApp({ channels: [lounge, group] });
handlers['channels-list']([lounge, group]);
assert.deepEqual(app.channels.map((c) => c.code), [lounge.code, group.code]);
});
test('a kick from a group says you were removed from the group, not kicked', () => {
const { app, handlers, toasts } = makeApp({ channels: [lounge, group], currentChannel: group.code });
handlers.kicked({ channelCode: group.code, group: true, reason: '' });
assert.equal(toasts[0].message, 'You were removed from Crew');
assert.equal(app.currentChannel, null);
handlers.kicked({ channelCode: group.code, group: true, reason: 'spam' });
assert.equal(toasts[1].message, 'You were removed from Crew: spam');
});
test('a kick from a channel names the channel instead of sounding like the server', () => {
const { toasts, handlers } = makeApp({ channels: [lounge] });
handlers.kicked({ channelCode: lounge.code, reason: '' });
handlers.kicked({ channelCode: lounge.code, reason: 'spam' });
handlers.kicked({ channelCode: 'ffffffff', reason: '' });
assert.deepEqual(toasts.map((x) => x.message), ['You were kicked from #lounge', 'You were kicked from #lounge: spam', 'You were kicked']);
});
test('a group that goes away closes in the pop-out DM window too', () => {
let closed = 0;
const { app, handlers } = makeApp({ channels: [lounge, group], _activeDMPip: group.code, _closeDMPiP() { closed++; this._activeDMPip = null; } });
handlers['channel-deleted']({ code: lounge.code });
assert.equal(closed, 0);
handlers['channel-deleted']({ code: group.code });
assert.equal(closed, 1);
assert.equal(app.channels.length, 0);
});