You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Add functions to assist in immutability of Event objects
This commit is contained in:
60
spec/unit/models/event.spec.ts
Normal file
60
spec/unit/models/event.spec.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2021 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 { MatrixEvent } from "../../../src/models/event";
|
||||
|
||||
describe('MatrixEvent', () => {
|
||||
it('should create copies of itself', () => {
|
||||
const a = new MatrixEvent({
|
||||
type: "com.example.test",
|
||||
content: {
|
||||
isTest: true,
|
||||
num: 42,
|
||||
},
|
||||
});
|
||||
|
||||
const clone = a.getSnapshotCopy();
|
||||
expect(clone).toBeDefined();
|
||||
expect(clone).not.toBe(a);
|
||||
expect(clone.event).not.toBe(a.event);
|
||||
expect(clone.event).toMatchObject(a.event);
|
||||
|
||||
// The other properties we're not super interested in, honestly.
|
||||
});
|
||||
|
||||
it('should compare itself to other events using json', () => {
|
||||
const a = new MatrixEvent({
|
||||
type: "com.example.test",
|
||||
content: {
|
||||
isTest: true,
|
||||
num: 42,
|
||||
},
|
||||
});
|
||||
const b = new MatrixEvent({
|
||||
type: "com.example.test______B",
|
||||
content: {
|
||||
isTest: true,
|
||||
num: 42,
|
||||
},
|
||||
});
|
||||
expect(a.isEquivalentTo(b)).toBe(false);
|
||||
expect(a.isEquivalentTo(a)).toBe(true);
|
||||
expect(b.isEquivalentTo(a)).toBe(false);
|
||||
expect(b.isEquivalentTo(b)).toBe(true);
|
||||
expect(a.getSnapshotCopy().isEquivalentTo(a)).toBe(true);
|
||||
expect(a.getSnapshotCopy().isEquivalentTo(b)).toBe(false);
|
||||
});
|
||||
});
|
@ -2,7 +2,7 @@ import * as utils from "../../src/utils";
|
||||
import {
|
||||
alphabetPad,
|
||||
averageBetweenStrings,
|
||||
baseToString,
|
||||
baseToString, deepSortedObjectEntries,
|
||||
DEFAULT_ALPHABET,
|
||||
lexicographicCompare,
|
||||
nextString,
|
||||
@ -429,4 +429,38 @@ describe("utils", function() {
|
||||
expect(lexicographicCompare('a', 'A') > 0).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deepSortedObjectEntries', () => {
|
||||
it('should auto-return non-objects', () => {
|
||||
expect(deepSortedObjectEntries(42)).toEqual(42);
|
||||
expect(deepSortedObjectEntries("not object")).toEqual("not object");
|
||||
expect(deepSortedObjectEntries(true)).toEqual(true);
|
||||
expect(deepSortedObjectEntries([42])).toEqual([42]);
|
||||
expect(deepSortedObjectEntries(null)).toEqual(null);
|
||||
expect(deepSortedObjectEntries(undefined)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should sort objects appropriately', () => {
|
||||
const input = {
|
||||
a: 42,
|
||||
b: {
|
||||
d: {},
|
||||
a: "test",
|
||||
b: "alpha",
|
||||
},
|
||||
[72]: "test",
|
||||
};
|
||||
const output = [
|
||||
["72", "test"],
|
||||
["a", 42],
|
||||
["b", [
|
||||
["a", "test"],
|
||||
["b", "alpha"],
|
||||
["d", []],
|
||||
]],
|
||||
];
|
||||
|
||||
expect(deepSortedObjectEntries(input)).toMatchObject(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -24,6 +24,7 @@ limitations under the License.
|
||||
import { EventEmitter } from 'events';
|
||||
import * as utils from '../utils';
|
||||
import { logger } from '../logger';
|
||||
import { deepSortedObjectEntries } from "../utils";
|
||||
|
||||
/**
|
||||
* Enum for event statuses.
|
||||
@ -1143,6 +1144,41 @@ utils.extend(MatrixEvent.prototype, {
|
||||
getTxnId() {
|
||||
return this._txnId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a copy/snapshot of this event. The returned copy will be loosely linked
|
||||
* back to this instance, though will have "frozen" event information. Other
|
||||
* properties may mutate depending on the state of this instance at the time
|
||||
* of snapshotting.
|
||||
*
|
||||
* This is meant to be used to snapshot the event details themselves, not the
|
||||
* features (such as sender) surrounding the event.
|
||||
* @returns {MatrixEvent} A snapshot of this event.
|
||||
*/
|
||||
getSnapshotCopy() {
|
||||
const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));
|
||||
for (const [p, v] of Object.entries(this)) {
|
||||
if (p !== "event") { // exclude the thing we just cloned
|
||||
ev[p] = v;
|
||||
}
|
||||
}
|
||||
return ev;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if this event is equivalent to the given event. This only checks
|
||||
* the event object itself, not the other properties of the event. Intended for
|
||||
* use with getSnapshotCopy() to identify events changing.
|
||||
* @param {MatrixEvent} otherEvent The other event to check against.
|
||||
* @returns {boolean} True if the events are the same, false otherwise.
|
||||
*/
|
||||
isEquivalentTo(otherEvent) {
|
||||
if (!otherEvent) return false;
|
||||
if (otherEvent === this) return true;
|
||||
const myProps = deepSortedObjectEntries(this.event);
|
||||
const theirProps = deepSortedObjectEntries(otherEvent.event);
|
||||
return JSON.stringify(myProps) === JSON.stringify(theirProps);
|
||||
},
|
||||
});
|
||||
|
||||
/* _REDACT_KEEP_KEY_MAP gives the keys we keep when an event is redacted
|
||||
|
25
src/utils.ts
25
src/utils.ts
@ -235,6 +235,31 @@ export function deepCompare(x: any, y: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of object properties/values (entries) then
|
||||
* sorts the result by key, recursively. The input object must
|
||||
* ensure it does not have loops. If the input is not an object
|
||||
* then it will be returned as-is.
|
||||
* @param {*} obj The object to get entries of
|
||||
* @returns {[string, *][]} The entries, sorted by key.
|
||||
*/
|
||||
export function deepSortedObjectEntries(obj: any): [string, any][] {
|
||||
if (typeof(obj) !== "object") return obj;
|
||||
|
||||
// Apparently these are object types...
|
||||
if (obj === null || obj === undefined || Array.isArray(obj)) return obj;
|
||||
|
||||
const pairs: [string, any][] = [];
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
pairs.push([k, deepSortedObjectEntries(v)]);
|
||||
}
|
||||
|
||||
// lexicographicCompare is faster than localeCompare, so let's use that.
|
||||
pairs.sort((a, b) => lexicographicCompare(a[0], b[0]));
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy properties from one object to another.
|
||||
*
|
||||
|
Reference in New Issue
Block a user