mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 17:25:50 +03:00
Polls: sync push rules on changes to account_data (#10287)
* 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 * extract updatePushRuleActions fn to utils * extract update synced rules * just synchronise in one place? * monitor account data changes AND trigger changes sync in notifications form * lint * setup LoggedInView test with enough mocks * test rule syncing in LoggedInView * strict fixes * more comments * one more comment
This commit is contained in:
parent
4c6f8ad122
commit
cef821c21b
5 changed files with 525 additions and 51 deletions
|
@ -70,6 +70,7 @@ import { IConfigOptions } from "../../IConfigOptions";
|
|||
import LeftPanelLiveShareWarning from "../views/beacon/LeftPanelLiveShareWarning";
|
||||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||
import { PipContainer } from "./PipContainer";
|
||||
import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushRules";
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
|
@ -165,6 +166,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
this.updateServerNoticeEvents();
|
||||
|
||||
this._matrixClient.on(ClientEvent.AccountData, this.onAccountData);
|
||||
// check push rules on start up as well
|
||||
monitorSyncedPushRules(this._matrixClient.getAccountData("m.push_rules"), this._matrixClient);
|
||||
this._matrixClient.on(ClientEvent.Sync, this.onSync);
|
||||
// Call `onSync` with the current state as well
|
||||
this.onSync(this._matrixClient.getSyncState(), null, this._matrixClient.getSyncStateData());
|
||||
|
@ -279,6 +282,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
if (event.getType() === "m.ignored_user_list") {
|
||||
dis.dispatch({ action: "ignore_state_changed" });
|
||||
}
|
||||
monitorSyncedPushRules(event, this._matrixClient);
|
||||
};
|
||||
|
||||
private onCompactLayoutChanged = (): void => {
|
||||
|
|
|
@ -15,18 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import {
|
||||
IAnnotatedPushRule,
|
||||
IPusher,
|
||||
PushRuleAction,
|
||||
IPushRule,
|
||||
PushRuleKind,
|
||||
RuleId,
|
||||
} from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -51,6 +43,10 @@ import TagComposer from "../elements/TagComposer";
|
|||
import { objectClone } from "../../../utils/objects";
|
||||
import { arrayDiff } from "../../../utils/arrays";
|
||||
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
|
||||
import {
|
||||
updateExistingPushRulesWithActions,
|
||||
updatePushRuleActions,
|
||||
} from "../../../utils/pushRules/updatePushRuleActions";
|
||||
|
||||
// TODO: this "view" component still has far too much application logic in it,
|
||||
// which should be factored out to other files.
|
||||
|
@ -187,7 +183,6 @@ const maximumVectorState = (
|
|||
|
||||
export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
private settingWatchers: string[];
|
||||
private pushProcessor: PushProcessor;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -215,8 +210,6 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
this.setState({ audioNotifications: value as boolean }),
|
||||
),
|
||||
];
|
||||
|
||||
this.pushProcessor = new PushProcessor(MatrixClientPeg.get());
|
||||
}
|
||||
|
||||
private get isInhibited(): boolean {
|
||||
|
@ -461,43 +454,6 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
await SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, checked);
|
||||
};
|
||||
|
||||
private setPushRuleActions = async (
|
||||
ruleId: IPushRule["rule_id"],
|
||||
kind: PushRuleKind,
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!actions) {
|
||||
await cli.setPushRuleEnabled("global", kind, ruleId, false);
|
||||
} else {
|
||||
await cli.setPushRuleActions("global", kind, ruleId, actions);
|
||||
await cli.setPushRuleEnabled("global", kind, ruleId, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updated syncedRuleIds from rule definition
|
||||
* If a rule does not exist it is ignored
|
||||
* Synced rules are updated sequentially
|
||||
* and stop at first error
|
||||
*/
|
||||
private updateSyncedRules = async (
|
||||
syncedRuleIds: VectorPushRuleDefinition["syncedRuleIds"],
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
// get synced rules that exist for user
|
||||
const syncedRules: ReturnType<PushProcessor["getPushRuleAndKindById"]>[] = syncedRuleIds
|
||||
?.map((ruleId) => this.pushProcessor.getPushRuleAndKindById(ruleId))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!syncedRules?.length) {
|
||||
return;
|
||||
}
|
||||
for (const { kind, rule: syncedRule } of syncedRules) {
|
||||
await this.setPushRuleActions(syncedRule.rule_id, kind, actions);
|
||||
}
|
||||
};
|
||||
|
||||
private onRadioChecked = async (rule: IVectorPushRule, checkedState: VectorState): Promise<void> => {
|
||||
this.setState({ phase: Phase.Persisting });
|
||||
|
||||
|
@ -538,8 +494,13 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
} else {
|
||||
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.ruleId];
|
||||
const actions = definition.vectorStateToActions[checkedState];
|
||||
await this.setPushRuleActions(rule.rule.rule_id, rule.rule.kind, actions);
|
||||
await this.updateSyncedRules(definition.syncedRuleIds, actions);
|
||||
// we should not encounter this
|
||||
// satisfies types
|
||||
if (!rule.rule) {
|
||||
throw new Error("Cannot update rule: push rule data is incomplete.");
|
||||
}
|
||||
await updatePushRuleActions(cli, rule.rule.rule_id, rule.rule.kind, actions);
|
||||
await updateExistingPushRulesWithActions(cli, definition.syncedRuleIds, actions);
|
||||
}
|
||||
|
||||
await this.refreshFromServer();
|
||||
|
|
108
src/utils/pushRules/monitorSyncedPushRules.ts
Normal file
108
src/utils/pushRules/monitorSyncedPushRules.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
import { RuleId, IAnnotatedPushRule } from "matrix-js-sdk/src/@types/PushRules";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { VectorPushRulesDefinitions, VectorPushRuleDefinition } from "../../notifications";
|
||||
import { updateExistingPushRulesWithActions } from "./updatePushRuleActions";
|
||||
|
||||
const pushRuleAndKindToAnnotated = (
|
||||
ruleAndKind: ReturnType<PushProcessor["getPushRuleAndKindById"]>,
|
||||
): IAnnotatedPushRule | undefined =>
|
||||
ruleAndKind
|
||||
? {
|
||||
...ruleAndKind.rule,
|
||||
kind: ruleAndKind.kind,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* Checks that any synced rules that exist a given rule are in sync
|
||||
* And updates any that are out of sync
|
||||
* Ignores ruleIds that do not exist for the user
|
||||
* @param matrixClient - cli
|
||||
* @param pushProcessor - processor used to retrieve current state of rules
|
||||
* @param ruleId - primary rule
|
||||
* @param definition - VectorPushRuleDefinition of the primary rule
|
||||
*/
|
||||
const monitorSyncedRule = async (
|
||||
matrixClient: MatrixClient,
|
||||
pushProcessor: PushProcessor,
|
||||
ruleId: RuleId | string,
|
||||
definition: VectorPushRuleDefinition,
|
||||
): Promise<void> => {
|
||||
const primaryRule = pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId));
|
||||
|
||||
if (!primaryRule) {
|
||||
return;
|
||||
}
|
||||
const syncedRules: IAnnotatedPushRule[] | undefined = definition.syncedRuleIds
|
||||
?.map((ruleId) => pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId)))
|
||||
.filter((n?: IAnnotatedPushRule): n is IAnnotatedPushRule => Boolean(n));
|
||||
|
||||
// no synced rules to manage
|
||||
if (!syncedRules?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryRuleVectorState = definition.ruleToVectorState(primaryRule);
|
||||
|
||||
const outOfSyncRules = syncedRules.filter(
|
||||
(syncedRule) => definition.ruleToVectorState(syncedRule) !== primaryRuleVectorState,
|
||||
);
|
||||
|
||||
if (outOfSyncRules.length) {
|
||||
await updateExistingPushRulesWithActions(
|
||||
matrixClient,
|
||||
// eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
|
||||
outOfSyncRules.map(({ rule_id }) => rule_id),
|
||||
primaryRule.actions,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* On changes to m.push_rules account data,
|
||||
* check that synced push rules are in sync with their primary rule,
|
||||
* and update any out of sync rules.
|
||||
* synced rules are defined in VectorPushRulesDefinitions
|
||||
* If updating a rule fails for any reason,
|
||||
* the error is caught and handled silently
|
||||
* @param accountDataEvent - MatrixEvent
|
||||
* @param matrixClient - cli
|
||||
* @returns Resolves when updates are complete
|
||||
*/
|
||||
export const monitorSyncedPushRules = async (
|
||||
accountDataEvent: MatrixEvent | undefined,
|
||||
matrixClient: MatrixClient,
|
||||
): Promise<void> => {
|
||||
if (accountDataEvent?.getType() !== EventType.PushRules) {
|
||||
return;
|
||||
}
|
||||
const pushProcessor = new PushProcessor(matrixClient);
|
||||
|
||||
Object.entries(VectorPushRulesDefinitions).forEach(async ([ruleId, definition]) => {
|
||||
try {
|
||||
await monitorSyncedRule(matrixClient, pushProcessor, ruleId, definition);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to fully synchronise push rules for ${ruleId}`, error);
|
||||
}
|
||||
});
|
||||
};
|
75
src/utils/pushRules/updatePushRuleActions.ts
Normal file
75
src/utils/pushRules/updatePushRuleActions.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { IPushRule, PushRuleAction, PushRuleKind } from "matrix-js-sdk/src/matrix";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
/**
|
||||
* Sets the actions for a given push rule id and kind
|
||||
* When actions are falsy, disables the rule
|
||||
* @param matrixClient - cli
|
||||
* @param ruleId - rule id to update
|
||||
* @param kind - PushRuleKind
|
||||
* @param actions - push rule actions to set for rule
|
||||
*/
|
||||
export const updatePushRuleActions = async (
|
||||
matrixClient: MatrixClient,
|
||||
ruleId: IPushRule["rule_id"],
|
||||
kind: PushRuleKind,
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
if (!actions) {
|
||||
await matrixClient.setPushRuleEnabled("global", kind, ruleId, false);
|
||||
} else {
|
||||
await matrixClient.setPushRuleActions("global", kind, ruleId, actions);
|
||||
await matrixClient.setPushRuleEnabled("global", kind, ruleId, true);
|
||||
}
|
||||
};
|
||||
|
||||
interface PushRuleAndKind {
|
||||
rule: IPushRule;
|
||||
kind: PushRuleKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update push rules with given actions
|
||||
* Where they already exist for current user
|
||||
* Rules are updated sequentially and stop at first error
|
||||
* @param matrixClient - cli
|
||||
* @param ruleIds - RuleIds of push rules to attempt to set actions for
|
||||
* @param actions - push rule actions to set for rule
|
||||
* @returns resolves when all rules have been updated
|
||||
* @returns rejects when a rule update fails
|
||||
*/
|
||||
export const updateExistingPushRulesWithActions = async (
|
||||
matrixClient: MatrixClient,
|
||||
ruleIds?: IPushRule["rule_id"][],
|
||||
actions?: PushRuleAction[],
|
||||
): Promise<void> => {
|
||||
const pushProcessor = new PushProcessor(matrixClient);
|
||||
|
||||
const rules: PushRuleAndKind[] | undefined = ruleIds
|
||||
?.map((ruleId) => pushProcessor.getPushRuleAndKindById(ruleId))
|
||||
.filter((n: PushRuleAndKind | null): n is PushRuleAndKind => Boolean(n));
|
||||
|
||||
if (!rules?.length) {
|
||||
return;
|
||||
}
|
||||
for (const { kind, rule } of rules) {
|
||||
await updatePushRuleActions(matrixClient, rule.rule_id, kind, actions);
|
||||
}
|
||||
};
|
326
test/components/structures/LoggedInView-test.tsx
Normal file
326
test/components/structures/LoggedInView-test.tsx
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
Copyright 2015 - 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult } from "@testing-library/react";
|
||||
import { ConditionKind, EventType, IPushRule, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import LoggedInView from "../../../src/components/structures/LoggedInView";
|
||||
import { SDKContext } from "../../../src/contexts/SDKContext";
|
||||
import { StandardActions } from "../../../src/notifications/StandardActions";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
|
||||
describe("<LoggedInView />", () => {
|
||||
const userId = "@alice:domain.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getAccountData: jest.fn(),
|
||||
getRoom: jest.fn(),
|
||||
getSyncState: jest.fn().mockReturnValue(null),
|
||||
getSyncStateData: jest.fn().mockReturnValue(null),
|
||||
getMediaHandler: jest.fn(),
|
||||
setPushRuleEnabled: jest.fn(),
|
||||
setPushRuleActions: jest.fn(),
|
||||
});
|
||||
const mediaHandler = new MediaHandler(mockClient);
|
||||
const mockSdkContext = new TestSdkContext();
|
||||
|
||||
const defaultProps = {
|
||||
matrixClient: mockClient,
|
||||
onRegistered: jest.fn(),
|
||||
resizeNotifier: new ResizeNotifier(),
|
||||
collapseLhs: false,
|
||||
hideToSRUsers: false,
|
||||
config: {
|
||||
brand: "Test",
|
||||
element_call: {},
|
||||
},
|
||||
currentRoomId: "",
|
||||
};
|
||||
|
||||
const getComponent = (props = {}): RenderResult =>
|
||||
render(<LoggedInView {...defaultProps} {...props} />, {
|
||||
wrapper: ({ children }) => <SDKContext.Provider value={mockSdkContext}>{children}</SDKContext.Provider>,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockClient.getMediaHandler.mockReturnValue(mediaHandler);
|
||||
mockClient.setPushRuleActions.mockReset().mockResolvedValue({});
|
||||
});
|
||||
|
||||
describe("synced push rules", () => {
|
||||
const pushRulesEvent = new MatrixEvent({ type: EventType.PushRules });
|
||||
|
||||
const oneToOneRule = {
|
||||
conditions: [
|
||||
{ kind: ConditionKind.RoomMemberCount, is: "2" },
|
||||
{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.message" },
|
||||
],
|
||||
actions: StandardActions.ACTION_NOTIFY,
|
||||
rule_id: ".m.rule.room_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
|
||||
const groupRule = {
|
||||
conditions: [{ kind: ConditionKind.EventMatch, key: "type", pattern: "m.room.message" }],
|
||||
actions: StandardActions.ACTION_NOTIFY,
|
||||
rule_id: ".m.rule.message",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
|
||||
const pollStartOneToOne = {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.RoomMemberCount,
|
||||
is: "2",
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "org.matrix.msc3381.poll.start",
|
||||
},
|
||||
],
|
||||
actions: StandardActions.ACTION_NOTIFY,
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_start_one_to_one",
|
||||
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: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_end_one_to_one",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
|
||||
const pollStartGroup = {
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "org.matrix.msc3381.poll.start",
|
||||
},
|
||||
],
|
||||
actions: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
|
||||
rule_id: ".org.matrix.msc3930.rule.poll_start",
|
||||
default: true,
|
||||
enabled: true,
|
||||
} as IPushRule;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.getAccountData.mockImplementation((eventType: string) =>
|
||||
eventType === EventType.PushRules ? pushRulesEvent : undefined,
|
||||
);
|
||||
setPushRules([]);
|
||||
// stub out error logger to avoid littering console
|
||||
jest.spyOn(logger, "error")
|
||||
.mockClear()
|
||||
.mockImplementation(() => {});
|
||||
|
||||
mockClient.setPushRuleActions.mockClear();
|
||||
mockClient.setPushRuleEnabled.mockClear();
|
||||
});
|
||||
|
||||
const setPushRules = (rules: IPushRule[] = []): void => {
|
||||
const pushRules = {
|
||||
global: {
|
||||
underride: [...rules],
|
||||
},
|
||||
};
|
||||
|
||||
mockClient.pushRules = pushRules;
|
||||
};
|
||||
|
||||
describe("on mount", () => {
|
||||
it("handles when user has no push rules event in account data", () => {
|
||||
mockClient.getAccountData.mockReturnValue(undefined);
|
||||
getComponent();
|
||||
|
||||
expect(mockClient.getAccountData).toHaveBeenCalledWith(EventType.PushRules);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles when user doesnt have a push rule defined in vector definitions", () => {
|
||||
// synced push rules uses VectorPushRulesDefinitions
|
||||
// rules defined there may not exist in m.push_rules
|
||||
// mock push rules with group rule, but missing oneToOne rule
|
||||
setPushRules([pollStartOneToOne, groupRule, pollStartGroup]);
|
||||
|
||||
getComponent();
|
||||
|
||||
// just called once for one-to-one
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(1);
|
||||
// set to match primary rule (groupRule)
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollStartGroup.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
});
|
||||
|
||||
it("updates all mismatched rules from synced rules", () => {
|
||||
setPushRules([
|
||||
// poll 1-1 rules are synced with oneToOneRule
|
||||
oneToOneRule, // on
|
||||
pollStartOneToOne, // on
|
||||
pollEndOneToOne, // loud
|
||||
// poll group rules are synced with groupRule
|
||||
groupRule, // on
|
||||
pollStartGroup, // loud
|
||||
]);
|
||||
|
||||
getComponent();
|
||||
|
||||
// only called for rules not in sync with their primary rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(2);
|
||||
// set to match primary rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollStartGroup.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollEndOneToOne.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
});
|
||||
|
||||
it("catches and logs errors while updating a rule", async () => {
|
||||
mockClient.setPushRuleActions.mockRejectedValueOnce("oups").mockResolvedValueOnce({});
|
||||
|
||||
setPushRules([
|
||||
// poll 1-1 rules are synced with oneToOneRule
|
||||
oneToOneRule, // on
|
||||
pollStartOneToOne, // on
|
||||
pollEndOneToOne, // loud
|
||||
// poll group rules are synced with groupRule
|
||||
groupRule, // on
|
||||
pollStartGroup, // loud
|
||||
]);
|
||||
|
||||
getComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledTimes(2);
|
||||
// both calls made
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollStartGroup.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
// second primary rule still updated after first rule failed
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollEndOneToOne.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
"Failed to fully synchronise push rules for .m.rule.room_one_to_one",
|
||||
"oups",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("on changes to account_data", () => {
|
||||
it("ignores other account data events", () => {
|
||||
// setup a push rule state with mismatched rules
|
||||
setPushRules([
|
||||
// poll 1-1 rules are synced with oneToOneRule
|
||||
oneToOneRule, // on
|
||||
pollEndOneToOne, // loud
|
||||
]);
|
||||
|
||||
getComponent();
|
||||
|
||||
mockClient.setPushRuleActions.mockClear();
|
||||
|
||||
const someOtherAccountData = new MatrixEvent({ type: "my-test-account-data " });
|
||||
mockClient.emit(ClientEvent.AccountData, someOtherAccountData);
|
||||
|
||||
// didnt check rule sync
|
||||
expect(mockClient.setPushRuleActions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates all mismatched rules from synced rules on a change to push rules account data", () => {
|
||||
// setup a push rule state with mismatched rules
|
||||
setPushRules([
|
||||
// poll 1-1 rules are synced with oneToOneRule
|
||||
oneToOneRule, // on
|
||||
pollEndOneToOne, // loud
|
||||
]);
|
||||
|
||||
getComponent();
|
||||
|
||||
mockClient.setPushRuleActions.mockClear();
|
||||
|
||||
mockClient.emit(ClientEvent.AccountData, pushRulesEvent);
|
||||
|
||||
// set to match primary rule
|
||||
expect(mockClient.setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
"underride",
|
||||
pollEndOneToOne.rule_id,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
});
|
||||
|
||||
it("stops listening to account data events on unmount", () => {
|
||||
// setup a push rule state with mismatched rules
|
||||
setPushRules([
|
||||
// poll 1-1 rules are synced with oneToOneRule
|
||||
oneToOneRule, // on
|
||||
pollEndOneToOne, // loud
|
||||
]);
|
||||
|
||||
const { unmount } = getComponent();
|
||||
|
||||
mockClient.setPushRuleActions.mockClear();
|
||||
|
||||
unmount();
|
||||
|
||||
mockClient.emit(ClientEvent.AccountData, pushRulesEvent);
|
||||
|
||||
// not called
|
||||
expect(mockClient.setPushRuleActions).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue