// What Admin → Teams → Notification bridge decides (client/src/lib/teamIntegrations.js). // // The test that earns this file: **repointing a row must not carry its // acknowledgement across.** That is the one way this screen could actively // mislead — an operator confirms a private channel, changes the id to a public // one, and the form still shows the confirmation as standing. The server clears // it either way, so the failure would be a screen that disagrees with the answer // it is about to get, which is worse than one that simply refuses. // // The rest is the boundary of the confirmation dialog: it must open when it // matters and stay shut when it does not, because a dialog that appears on saves // that did not need it is one people learn to click through. import { test } from 'node:test' import assert from 'node:assert/strict' import { eventLabel, rowKey, isDefaultRow, blankDraft, draftFrom, appliesToLabel, toggleEvent, setChannel, carriesMembersOnly, needsAcknowledgement, membersOnlyIdsOf, availableTargets, } from '../src/lib/teamIntegrations.js' const MEMBERS_ONLY = ['team.forum.post', 'team.announcement'] const ROSTER = 'team.member.joined' const FORUM = 'team.forum.post' const draft = (over = {}) => ({ ...blankDraft(null), ...over }) // ── The acknowledgement dies with its channel ────────────────────────────── test('changing the channel drops a standing acknowledgement', () => { const before = draft({ channelRef: '111', membersAck: true, events: [FORUM], enabled: true }) const after = setChannel(before, '222') assert.equal(after.membersAck, false) assert.equal(after.channelRef, '222') }) test('setting the SAME channel does not clear it — an unrelated re-render is not a repoint', () => { const before = draft({ channelRef: '111', membersAck: true }) const after = setChannel(before, '111') assert.equal(after.membersAck, true) assert.equal(after, before, 'and the object is returned unchanged, so nothing re-renders') }) test('a repointed row needs the dialog again, which is the whole point of clearing it', () => { const before = draft({ channelRef: '111', membersAck: true, events: [FORUM], enabled: true }) assert.equal(needsAcknowledgement(before, MEMBERS_ONLY), false) assert.equal(needsAcknowledgement(setChannel(before, '222'), MEMBERS_ONLY), true) }) // ── When the dialog opens ────────────────────────────────────────────────── test('enabling a forum event without the tick asks first', () => { assert.equal(needsAcknowledgement(draft({ events: [FORUM], enabled: true }), MEMBERS_ONLY), true) }) test('a DISABLED draft carrying forum events does not ask — nothing is being published yet', () => { assert.equal(needsAcknowledgement(draft({ events: [FORUM], enabled: false }), MEMBERS_ONLY), false) }) test('a roster-only bridge never asks, however it is configured', () => { assert.equal(needsAcknowledgement(draft({ events: [ROSTER], enabled: true }), MEMBERS_ONLY), false) assert.equal(carriesMembersOnly(draft({ events: [ROSTER] }), MEMBERS_ONLY), false) }) test('an acknowledgement already given means no second dialog for an unrelated edit', () => { const d = draft({ events: [FORUM], enabled: true, membersAck: true, channelRef: '111' }) const withRoster = toggleEvent(d, ROSTER) assert.equal(needsAcknowledgement(withRoster, MEMBERS_ONLY), false) }) test('the members-only set comes from the server, not from a list held here', () => { // The client must not decide what is members-only: a future stream added // server-side would silently escape a hardcoded client list. assert.deepEqual( membersOnlyIdsOf([{ id: ROSTER, membersOnly: false }, { id: FORUM, membersOnly: true }]), [FORUM], ) // Told nothing is members-only, the dialog never opens — the server is the one // that would then refuse, which is the correct division. assert.equal(needsAcknowledgement(draft({ events: [FORUM], enabled: true }), []), false) }) // ── Events, rows and targets ─────────────────────────────────────────────── test('toggling adds then removes, and preserves selection order', () => { let d = draft() d = toggleEvent(d, FORUM) d = toggleEvent(d, ROSTER) assert.deepEqual(d.events, [FORUM, ROSTER]) d = toggleEvent(d, FORUM) assert.deepEqual(d.events, [ROSTER]) }) test('the default row is identified by a NULL team, and an undefined one counts too', () => { assert.equal(isDefaultRow({ team_id: null }), true) assert.equal(isDefaultRow({}), true) assert.equal(isDefaultRow({ team_id: 4 }), false) assert.equal(rowKey({ team_id: null }), 'default') assert.equal(rowKey({ team_id: 4 }), '4') }) test('a row is labelled by the staff override first, then the name, then its id', () => { assert.equal(appliesToLabel({ team_id: null }), 'All Teams') assert.equal(appliesToLabel({ team_id: 4, team_name: 'Real', display_name_override: 'Shown' }), 'Shown') assert.equal(appliesToLabel({ team_id: 4, team_name: 'Real' }), 'Real') assert.equal(appliesToLabel({ team_id: 4 }), 'Team #4') }) test('a Team that already has an override is not offered a second one', () => { const rows = [{ team_id: null }, { team_id: 2 }] const teams = [{ id: 1, status: 'active' }, { id: 2, status: 'active' }, { id: 3, status: 'archived' }] const { hasDefault, teams: available } = availableTargets(rows, teams) assert.equal(hasDefault, true) assert.deepEqual(available.map((t) => t.id), [1], 'the taken one and the archived one are both out') }) test('with no default configured, the default is still offered', () => { const { hasDefault } = availableTargets([{ team_id: 2 }], []) assert.equal(hasDefault, false) }) test('a row round-trips through the draft without changing what it means', () => { const row = { team_id: 4, events: [FORUM], channel_ref: '111', enabled: 1, members_ack: 1 } assert.deepEqual(draftFrom(row), { teamId: 4, events: [FORUM], channelRef: '111', enabled: true, membersAck: true }) }) test('an unknown event id renders as itself rather than as blank', () => { assert.equal(eventLabel(FORUM), 'New forum post') assert.equal(eventLabel('team.something.new'), 'team.something.new') })