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 {
|
import {
|
||||||
alphabetPad,
|
alphabetPad,
|
||||||
averageBetweenStrings,
|
averageBetweenStrings,
|
||||||
baseToString,
|
baseToString, deepSortedObjectEntries,
|
||||||
DEFAULT_ALPHABET,
|
DEFAULT_ALPHABET,
|
||||||
lexicographicCompare,
|
lexicographicCompare,
|
||||||
nextString,
|
nextString,
|
||||||
@ -429,4 +429,38 @@ describe("utils", function() {
|
|||||||
expect(lexicographicCompare('a', 'A') > 0).toBe(true);
|
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 { EventEmitter } from 'events';
|
||||||
import * as utils from '../utils';
|
import * as utils from '../utils';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
import { deepSortedObjectEntries } from "../utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for event statuses.
|
* Enum for event statuses.
|
||||||
@ -1143,6 +1144,41 @@ utils.extend(MatrixEvent.prototype, {
|
|||||||
getTxnId() {
|
getTxnId() {
|
||||||
return this._txnId;
|
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
|
/* _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;
|
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.
|
* Copy properties from one object to another.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user