Polls push rules: synchronise poll rules with message rules (#10263)
* basic sync setup * formatting * get loudest value for synced rules * more types * test synced rules in notifications settings * type fixes * noimplicitany fixes * remove debug * tidying
This commit is contained in:
parent
e5291c195d
commit
10a765472b
3 changed files with 403 additions and 50 deletions
|
@ -23,6 +23,9 @@ import {
|
|||
Room,
|
||||
NotificationCountType,
|
||||
PushRuleActionName,
|
||||
TweakName,
|
||||
ConditionKind,
|
||||
IPushRuleCondition,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||
import { act, fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
|
||||
|
@ -47,27 +50,42 @@ const masterRule: IPushRule = {
|
|||
};
|
||||
const oneToOneRule: IPushRule = {
|
||||
conditions: [
|
||||
{ kind: "room_member_count", is: "2" },
|
||||
{ kind: "event_match", key: "type", pattern: "m.room.message" },
|
||||
{ kind: ConditionKind.RoomMemberCount, is: "2" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.message" },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "highlight", value: false }],
|
||||
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: false }],
|
||||
rule_id: ".m.rule.room_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
const encryptedOneToOneRule: IPushRule = {
|
||||
conditions: [
|
||||
{ kind: "room_member_count", is: "2" },
|
||||
{ kind: "event_match", key: "type", pattern: "m.room.encrypted" },
|
||||
{ kind: ConditionKind.RoomMemberCount, is: "2" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.encrypted" },
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
{ set_tweak: TweakName.Highlight, value: false },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "sound", value: "default" }, { set_tweak: "highlight", value: false }],
|
||||
rule_id: ".m.rule.encrypted_room_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
const groupRule = {
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.message" }],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
{ set_tweak: TweakName.Highlight, value: false },
|
||||
],
|
||||
rule_id: ".m.rule.message",
|
||||
default: true,
|
||||
enabled: true,
|
||||
};
|
||||
const encryptedGroupRule: IPushRule = {
|
||||
conditions: [{ kind: "event_match", key: "type", pattern: "m.room.encrypted" }],
|
||||
actions: ["dont_notify"],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.encrypted" }],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".m.rule.encrypted",
|
||||
default: true,
|
||||
enabled: true,
|
||||
|
@ -76,46 +94,55 @@ const pushRules: IPushRules = {
|
|||
global: {
|
||||
underride: [
|
||||
{
|
||||
conditions: [{ kind: "event_match", key: "type", pattern: "m.call.invite" }],
|
||||
actions: ["notify", { set_tweak: "sound", value: "ring" }, { set_tweak: "highlight", value: false }],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.call.invite" }],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "ring" },
|
||||
{ set_tweak: TweakName.Highlight, value: false },
|
||||
],
|
||||
rule_id: ".m.rule.call",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
oneToOneRule,
|
||||
encryptedOneToOneRule,
|
||||
{
|
||||
conditions: [{ kind: "event_match", key: "type", pattern: "m.room.message" }],
|
||||
actions: ["notify", { set_tweak: "sound", value: "default" }, { set_tweak: "highlight", value: false }],
|
||||
rule_id: ".m.rule.message",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
groupRule,
|
||||
encryptedGroupRule,
|
||||
{
|
||||
conditions: [
|
||||
{ kind: "event_match", key: "type", pattern: "im.vector.modular.widgets" },
|
||||
{ kind: "event_match", key: "content.type", pattern: "jitsi" },
|
||||
{ kind: "event_match", key: "state_key", pattern: "*" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "im.vector.modular.widgets" },
|
||||
{ kind: ConditionKind.EventMatch, key: "content.type", pattern: "jitsi" },
|
||||
{ kind: ConditionKind.EventMatch, key: "state_key", pattern: "*" },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "highlight", value: false }],
|
||||
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: false }],
|
||||
rule_id: ".im.vector.jitsi",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
sender: [],
|
||||
room: [{ actions: ["dont_notify"], rule_id: "!zJPyWqpMorfCcWObge:matrix.org", default: false, enabled: true }],
|
||||
room: [
|
||||
{
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: "!zJPyWqpMorfCcWObge:matrix.org",
|
||||
default: false,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
content: [
|
||||
{
|
||||
actions: ["notify", { set_tweak: "highlight", value: false }],
|
||||
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: false }],
|
||||
pattern: "banana",
|
||||
rule_id: "banana",
|
||||
default: false,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
actions: ["notify", { set_tweak: "sound", value: "default" }, { set_tweak: "highlight" }],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
{ set_tweak: TweakName.Highlight },
|
||||
],
|
||||
pattern: "kadev1",
|
||||
rule_id: ".m.rule.contains_user_name",
|
||||
default: true,
|
||||
|
@ -123,62 +150,76 @@ const pushRules: IPushRules = {
|
|||
},
|
||||
],
|
||||
override: [
|
||||
{ conditions: [], actions: ["dont_notify"], rule_id: ".m.rule.master", default: true, enabled: false },
|
||||
{
|
||||
conditions: [{ kind: "event_match", key: "content.msgtype", pattern: "m.notice" }],
|
||||
actions: ["dont_notify"],
|
||||
conditions: [],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".m.rule.master",
|
||||
default: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "content.msgtype", pattern: "m.notice" }],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".m.rule.suppress_notices",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [
|
||||
{ kind: "event_match", key: "type", pattern: "m.room.member" },
|
||||
{ kind: "event_match", key: "content.membership", pattern: "invite" },
|
||||
{ kind: "event_match", key: "state_key", pattern: "@kadev1:matrix.org" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.member" },
|
||||
{ kind: ConditionKind.EventMatch, key: "content.membership", pattern: "invite" },
|
||||
{ kind: ConditionKind.EventMatch, key: "state_key", pattern: "@kadev1:matrix.org" },
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
{ set_tweak: TweakName.Highlight, value: false },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "sound", value: "default" }, { set_tweak: "highlight", value: false }],
|
||||
rule_id: ".m.rule.invite_for_me",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [{ kind: "event_match", key: "type", pattern: "m.room.member" }],
|
||||
actions: ["dont_notify"],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.member" }],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".m.rule.member_event",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [{ kind: "contains_display_name" }],
|
||||
actions: ["notify", { set_tweak: "sound", value: "default" }, { set_tweak: "highlight" }],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
{ set_tweak: TweakName.Highlight },
|
||||
],
|
||||
rule_id: ".m.rule.contains_display_name",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [
|
||||
{ kind: "event_match", key: "content.body", pattern: "@room" },
|
||||
{ kind: ConditionKind.EventMatch, key: "content.body", pattern: "@room" },
|
||||
{ kind: "sender_notification_permission", key: "room" },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "highlight", value: true }],
|
||||
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: true }],
|
||||
rule_id: ".m.rule.roomnotif",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [
|
||||
{ kind: "event_match", key: "type", pattern: "m.room.tombstone" },
|
||||
{ kind: "event_match", key: "state_key", pattern: "" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.tombstone" },
|
||||
{ kind: ConditionKind.EventMatch, key: "state_key", pattern: "" },
|
||||
],
|
||||
actions: ["notify", { set_tweak: "highlight", value: true }],
|
||||
actions: [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: true }],
|
||||
rule_id: ".m.rule.tombstone",
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
conditions: [{ kind: "event_match", key: "type", pattern: "m.reaction" }],
|
||||
actions: ["dont_notify"],
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.reaction" }],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".m.rule.reaction",
|
||||
default: true,
|
||||
enabled: true,
|
||||
|
@ -231,6 +272,8 @@ describe("<Notifications />", () => {
|
|||
mockClient.getPushers.mockClear().mockResolvedValue({ pushers: [] });
|
||||
mockClient.getThreePids.mockClear().mockResolvedValue({ threepids: [] });
|
||||
mockClient.setPusher.mockClear().mockResolvedValue({});
|
||||
mockClient.setPushRuleActions.mockClear().mockResolvedValue({});
|
||||
mockClient.pushRules = pushRules;
|
||||
});
|
||||
|
||||
it("renders spinner while loading", async () => {
|
||||
|
@ -429,6 +472,196 @@ describe("<Notifications />", () => {
|
|||
StandardActions.ACTION_DONT_NOTIFY,
|
||||
);
|
||||
});
|
||||
|
||||
describe("synced rules", () => {
|
||||
const pollStartOneToOne = {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.RoomMemberCount,
|
||||
is: "2",
|
||||
} as IPushRuleCondition<ConditionKind.RoomMemberCount>,
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "org.matrix.msc3381.poll.start",
|
||||
} as IPushRuleCondition<ConditionKind.EventMatch>,
|
||||
],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_start_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
const pollStartGroup = {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "org.matrix.msc3381.poll.start",
|
||||
},
|
||||
],
|
||||
actions: [PushRuleActionName.Notify],
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_start",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
const pollEndOneToOne = {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.RoomMemberCount,
|
||||
is: "2",
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "org.matrix.msc3381.poll.end",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{ set_tweak: TweakName.Highlight, value: false },
|
||||
{ set_tweak: TweakName.Sound, value: "default" },
|
||||
],
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_end_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
|
||||
const setPushRuleMock = (rules: IPushRule[] = []): void => {
|
||||
const combinedRules = {
|
||||
...pushRules,
|
||||
global: {
|
||||
...pushRules.global,
|
||||
underride: [...pushRules.global.underride!, ...rules],
|
||||
},
|
||||
};
|
||||
mockClient.getPushRules.mockClear().mockResolvedValue(combinedRules);
|
||||
mockClient.pushRules = combinedRules;
|
||||
};
|
||||
|
||||
// ".m.rule.room_one_to_one" and ".m.rule.message" have synced rules
|
||||
it("succeeds when no synced rules exist for user", async () => {
|
||||
await getComponentAndWait();
|
||||
const section = "vector_global";
|
||||
|
||||
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||
|
||||
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// didnt attempt to update any non-existant rules
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(1);
|
||||
|
||||
// no error
|
||||
expect(screen.queryByTestId("error-message")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates synced rules when they exist for user", async () => {
|
||||
setPushRuleMock([pollStartOneToOne, pollStartGroup]);
|
||||
await getComponentAndWait();
|
||||
const section = "vector_global";
|
||||
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||
|
||||
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// updated synced rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
oneToOneRule.rule_id,
|
||||
[PushRuleActionName.DontNotify],
|
||||
);
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollStartOneToOne.rule_id,
|
||||
[PushRuleActionName.DontNotify],
|
||||
);
|
||||
// only called for parent rule and one existing synced rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(2);
|
||||
|
||||
// no error
|
||||
expect(screen.queryByTestId("error-message")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not update synced rules when main rule update fails", async () => {
|
||||
setPushRuleMock([pollStartOneToOne]);
|
||||
await getComponentAndWait();
|
||||
const section = "vector_global";
|
||||
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||
// have main rule update fail
|
||||
mockClient.setPushRuleActions.mockRejectedValue("oups");
|
||||
|
||||
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
oneToOneRule.rule_id,
|
||||
[PushRuleActionName.DontNotify],
|
||||
);
|
||||
// only called for parent rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.queryByTestId("error-message")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sets the UI toggle to rule value when no synced rule exist for the user", async () => {
|
||||
setPushRuleMock([]);
|
||||
await getComponentAndWait();
|
||||
const section = "vector_global";
|
||||
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||
|
||||
// loudest state of synced rules should be the toggle value
|
||||
expect(oneToOneRuleElement.querySelector('input[aria-label="On"]')).toBeChecked();
|
||||
});
|
||||
|
||||
it("sets the UI toggle to the loudest synced rule value", async () => {
|
||||
// oneToOneRule is set to 'On'
|
||||
// pollEndOneToOne is set to 'Loud'
|
||||
setPushRuleMock([pollStartOneToOne, pollEndOneToOne]);
|
||||
await getComponentAndWait();
|
||||
const section = "vector_global";
|
||||
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
|
||||
|
||||
// loudest state of synced rules should be the toggle value
|
||||
expect(oneToOneRuleElement.querySelector('input[aria-label="Noisy"]')).toBeChecked();
|
||||
|
||||
const onToggle = oneToOneRuleElement.querySelector('input[aria-label="On"]')!;
|
||||
fireEvent.click(onToggle);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// called for all 3 rules
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(3);
|
||||
const expectedActions = [PushRuleActionName.Notify, { set_tweak: TweakName.Highlight, value: false }];
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
oneToOneRule.rule_id,
|
||||
expectedActions,
|
||||
);
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollStartOneToOne.rule_id,
|
||||
expectedActions,
|
||||
);
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollEndOneToOne.rule_id,
|
||||
expectedActions,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("clear all notifications", () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue