Skip to content

Support for creator power level #4937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 8, 2025
Merged
164 changes: 19 additions & 145 deletions spec/unit/room-member.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import * as utils from "../test-utils/test-utils";
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
import {
createClient,
EventType,
type MatrixClient,
MatrixEvent,
type RoomState,
UNSTABLE_MSC2666_MUTUAL_ROOMS,
UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS,
Expand Down Expand Up @@ -101,158 +101,32 @@ describe("RoomMember", function () {
});
});

describe("setPowerLevelEvent", function () {
it("should set 'powerLevel' and 'powerLevelNorm'.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@bertha:bar": 200,
"@invalid:user": 10, // shouldn't barf on this.
},
},
event: true,
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(member.powerLevelNorm).toEqual(10);

const memberB = new RoomMember(roomId, userB);
memberB.setPowerLevelEvent(event);
expect(memberB.powerLevel).toEqual(200);
expect(memberB.powerLevelNorm).toEqual(100);
describe("setPowerLevel", function () {
it("should set 'powerLevel'.", function () {
member.setPowerLevel(0, new MatrixEvent());
expect(member.powerLevel).toEqual(0);
member.setPowerLevel(200, new MatrixEvent());
expect(member.powerLevel).toEqual(200);
});

it("should emit 'RoomMember.powerLevel' if the power level changes.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@bertha:bar": 200,
"@invalid:user": 10, // shouldn't barf on this.
},
},
event: true,
});
let emitCount = 0;
it("should emit when power level set", function () {
const onEmit = jest.fn();
member.on(RoomMemberEvent.PowerLevel, onEmit);

member.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember).toEqual(member);
expect(emitEvent).toEqual(event);
});
const aMatrixEvent = new MatrixEvent();
member.setPowerLevel(10, aMatrixEvent);

member.setPowerLevelEvent(event);
expect(emitCount).toEqual(1);
member.setPowerLevelEvent(event); // no-op
expect(emitCount).toEqual(1);
expect(onEmit).toHaveBeenCalledWith(aMatrixEvent, member);
});

it("should honour power levels of zero.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": 0,
},
},
event: true,
});
let emitCount = 0;

// set the power level to something other than zero or we
// won't get an event
member.powerLevel = 1;
member.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(event);
});

member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(0);
expect(emitCount).toEqual(1);
});

it("should not honor string power levels.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
let emitCount = 0;
it("should not emit if new power level is the same", function () {
const onEmit = jest.fn();
member.on(RoomMemberEvent.PowerLevel, onEmit);

member.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});

member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});
const aMatrixEvent = new MatrixEvent();
member.setPowerLevel(0, aMatrixEvent);

it("should no-op if given a non-state or unrelated event", () => {
const fn = jest.spyOn(member, "emit");
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
member.setPowerLevelEvent(
utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
skey: "invalid",
event: true,
}),
);
const nonStateEv = utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
delete nonStateEv.event.state_key;
member.setPowerLevelEvent(nonStateEv);
member.setPowerLevelEvent(
utils.mkEvent({
type: EventType.Sticker,
room: roomId,
user: userA,
content: {},
event: true,
}),
);
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
expect(onEmit).not.toHaveBeenCalled();
});
});

Expand Down
161 changes: 156 additions & 5 deletions spec/unit/room-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as utils from "../test-utils/test-utils";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import { RoomMemberEvent } from "../../src/models/room-member";
import { type Beacon, BeaconEvent, getBeaconInfoIdentifier } from "../../src/models/beacon";
import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@types/event";
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
Expand Down Expand Up @@ -259,7 +260,7 @@ describe("RoomState", function () {
expect(emitCount).toEqual(2);
});

it("should call setPowerLevelEvent on each RoomMember for m.room.power_levels", function () {
it("should call setPowerLevel on each RoomMember for m.room.power_levels", function () {
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
Expand All @@ -273,12 +274,12 @@ describe("RoomState", function () {
});

// spy on the room members
jest.spyOn(state.members[userA], "setPowerLevelEvent");
jest.spyOn(state.members[userB], "setPowerLevelEvent");
jest.spyOn(state.members[userA], "setPowerLevel");
jest.spyOn(state.members[userB], "setPowerLevel");
state.setStateEvents([powerLevelEvent]);

expect(state.members[userA].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
expect(state.members[userB].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
expect(state.members[userA].setPowerLevel).toHaveBeenCalledWith(10, powerLevelEvent);
expect(state.members[userB].setPowerLevel).toHaveBeenCalledWith(10, powerLevelEvent);
});

it("should call setPowerLevelEvent on a new RoomMember if power levels exist", function () {
Expand Down Expand Up @@ -310,6 +311,156 @@ describe("RoomState", function () {
expect(state.members[userC].powerLevel).toEqual(10);
});

it("should calculate power level correctly", function () {
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
[userB]: 200,
"@invalid:user": 10, // shouldn't barf on this.
},
},
event: true,
});
state.setStateEvents([powerLevelEvent]);

expect(state.getMember(userA)?.powerLevel).toEqual(20);
expect(state.getMember(userB)?.powerLevel).toEqual(200);
});

it("should set 'powerLevel' with a v12 room.", function () {
const createEventV12 = utils.mkEvent({
type: "m.room.create",
room: roomId,
sender: userA,
content: { room_version: "12" },
event: true,
});
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
[userB]: 200,
"@invalid:user": 10, // shouldn't barf on this.
},
},
event: true,
});
state.setStateEvents([createEventV12, powerLevelEvent]);
expect(state.getMember(userA)?.powerLevel).toEqual(Infinity);
});

it("should honour power levels of zero.", function () {
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": 0,
},
},
event: true,
});
let emitCount = 0;

const memberA = state.getMember(userA)!;
// set the power level to something other than zero or we
// won't get an event
memberA.powerLevel = 1;
memberA.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(powerLevelEvent);
});

state.setStateEvents([powerLevelEvent]);
expect(memberA.powerLevel).toEqual(0);
expect(emitCount).toEqual(1);
});

it("should not honor string power levels.", function () {
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
let emitCount = 0;

const memberA = state.getMember(userA)!;
memberA.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(powerLevelEvent);
});

state.setStateEvents([powerLevelEvent]);
expect(memberA.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});

it("should no-op if given a non-state or unrelated event", () => {
const memberA = state.getMember(userA)!;
const fn = jest.spyOn(memberA, "emit");
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);

const powerLevelEvent = utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
skey: "invalid",
event: true,
});

state.setStateEvents([powerLevelEvent]);
const nonStateEv = utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
delete nonStateEv.event.state_key;
state.setStateEvents([nonStateEv]);
state.setStateEvents([
utils.mkEvent({
type: EventType.Sticker,
room: roomId,
user: userA,
content: {},
event: true,
}),
]);
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
});

it("should call setMembershipEvent on the right RoomMember", function () {
const memberEvent = utils.mkMembership({
user: userB,
Expand Down
Loading
Loading