Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137

This commit is contained in:
SBiOSoftWhare
2021-12-03 11:47:24 +01:00
parent 8d15fe55a2
commit e7d4cd7707
475 changed files with 87437 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,114 @@
/*
Copyright 2016 OpenMarket Ltd
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 <XCTest/XCTest.h>
#import "MXEncryptedAttachments.h"
#import "MXEncryptedContentFile.h"
#import "MXBase64Tools.h"
@interface EncryptedAttachmentsTest : XCTestCase
@end
@implementation EncryptedAttachmentsTest
- (void)setUp {
[super setUp];
}
- (void)tearDown {
[super tearDown];
}
- (void)testDecrypt {
NSArray *testVectors =
@[
@[@"", @{
@"v": @"v1",
@"hashes": @{
@"sha256": @"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"
},
@"key": @{
@"alg": @"A256CTR",
@"k": @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
@"key_ops": @[@"encrypt", @"decrypt"],
@"kty": @"oct"
},
@"iv": @"AAAAAAAAAAAAAAAAAAAAAA"
}, @""],
@[@"5xJZTt5cQicm+9f4", @{
@"v": @"v1",
@"hashes": @{
@"sha256": @"YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"
}, @"key": @{
@"alg": @"A256CTR",
@"k": @"__________________________________________8",
@"key_ops": @[@"encrypt", @"decrypt"],
@"kty": @"oct"
}, @"iv": @"//////////8AAAAAAAAAAA"
}, @"SGVsbG8sIFdvcmxk"],
@[@"zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q", @{
@"v": @"v2",
@"hashes": @{
@"sha256": @"IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"
},
@"key": @{
@"kty": @"oct",
@"key_ops": @[@"encrypt",@"decrypt"],
@"k": @"__________________________________________8",
@"alg": @"A256CTR"
},
@"iv": @"//////////8AAAAAAAAAAA"
}, @"YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ"],
@[@"tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA", @{
@"v": @"v1",
@"hashes": @{
@"sha256": @"LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"
},
@"key": @{
@"kty": @"oct",
@"key_ops": @[@"encrypt",@"decrypt"],
@"k": @"__________________________________________8",
@"alg": @"A256CTR"
},
@"iv": @"/////////////////////w"
}, @"YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ"]
];
for (NSArray *vector in testVectors) {
NSString *inputCiphertext = vector[0];
MXEncryptedContentFile *inputInfo = [MXEncryptedContentFile modelFromJSON:vector[1]];
NSString *want = vector[2];
NSData *ctData = [[NSData alloc] initWithBase64EncodedString:[MXBase64Tools padBase64:inputCiphertext] options:0];
NSInputStream *inputStream = [NSInputStream inputStreamWithData:ctData];
NSOutputStream *outputStream = [NSOutputStream outputStreamToMemory];
[MXEncryptedAttachments decryptAttachment:inputInfo inputStream:inputStream outputStream:outputStream success:^{
NSData *gotData = [outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
NSData *wantData = [[NSData alloc] initWithBase64EncodedString:[MXBase64Tools padBase64:want] options:0];
XCTAssertEqualObjects(wantData, gotData, "Decrypted data did not match expectation.");
} failure:^(NSError *error) {
XCTFail();
}];
}
}
@end

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,24 @@
/*
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 "MXKEventFormatter.h"
@interface MXKEventFormatter (Tests)
- (NSString*)userDisplayNameFromContentInEvent:(MXEvent*)event withMembershipFilter:(NSString *)filter;
- (NSString*)userAvatarUrlFromContentInEvent:(MXEvent*)event withMembershipFilter:(NSString *)filter;
@end

View File

@@ -0,0 +1,435 @@
/*
Copyright 2016 OpenMarket Ltd
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 <XCTest/XCTest.h>
#import "MatrixKit.h"
#import "MXKEventFormatter+Tests.h"
@import DTCoreText;
@interface MXEventFormatterTests : XCTestCase
{
MXKEventFormatter *eventFormatter;
MXEvent *anEvent;
CGFloat maxHeaderSize;
}
@end
@implementation MXEventFormatterTests
- (void)setUp
{
[super setUp];
// Create a minimal event formatter
// Note: it may not be enough for testing all MXKEventFormatter methods
eventFormatter = [[MXKEventFormatter alloc] initWithMatrixSession:nil];
eventFormatter.treatMatrixUserIdAsLink = YES;
eventFormatter.treatMatrixRoomIdAsLink = YES;
eventFormatter.treatMatrixRoomAliasAsLink = YES;
eventFormatter.treatMatrixEventIdAsLink = YES;
anEvent = [[MXEvent alloc] init];
anEvent.roomId = @"aRoomId";
anEvent.eventId = @"anEventId";
anEvent.wireType = kMXEventTypeStringRoomMessage;
anEvent.originServerTs = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
anEvent.wireContent = @{
@"msgtype": kMXMessageTypeText,
@"body": @"deded",
};
maxHeaderSize = ceil(eventFormatter.defaultTextFont.pointSize * 1.2);
}
- (void)tearDown
{
[super tearDown];
}
- (void)testRenderHTMLStringWithHeaders
{
// Given HTML strings with h1/h2/h3 tags
NSString *h1HTML = @"<h1>Large Heading</h1>";
NSString *h2HTML = @"<h2>Smaller Heading</h2>";
NSString *h3HTML = @"<h3>Acceptable Heading</h3>";
// When rendering these strings as attributed strings
NSAttributedString *h1AttributedString = [eventFormatter renderHTMLString:h1HTML forEvent:anEvent withRoomState:nil];
NSAttributedString *h2AttributedString = [eventFormatter renderHTMLString:h2HTML forEvent:anEvent withRoomState:nil];
NSAttributedString *h3AttributedString = [eventFormatter renderHTMLString:h3HTML forEvent:anEvent withRoomState:nil];
// Then the h1/h2 fonts should be reduced in size to match h3.
XCTAssertEqualObjects(h1AttributedString.string, @"Large Heading", @"The text from an H1 tag should be preserved when removing formatting.");
XCTAssertEqualObjects(h2AttributedString.string, @"Smaller Heading", @"The text from an H2 tag should be preserved when removing formatting.");
XCTAssertEqualObjects(h3AttributedString.string, @"Acceptable Heading", @"The text from an H3 tag should not change.");
[h1AttributedString enumerateAttributesInRange:NSMakeRange(0, h1AttributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attributes[NSFontAttributeName];
XCTAssertGreaterThan(font.pointSize, eventFormatter.defaultTextFont.pointSize, @"H1 tags should be larger than the default body size.");
XCTAssertLessThanOrEqual(font.pointSize, maxHeaderSize, @"H1 tags shouldn't exceed the max header size.");
}];
[h2AttributedString enumerateAttributesInRange:NSMakeRange(0, h2AttributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attributes[NSFontAttributeName];
XCTAssertGreaterThan(font.pointSize, eventFormatter.defaultTextFont.pointSize, @"H2 tags should be larger than the default body size.");
XCTAssertLessThanOrEqual(font.pointSize, maxHeaderSize, @"H2 tags shouldn't exceed the max header size.");
}];
[h3AttributedString enumerateAttributesInRange:NSMakeRange(0, h3AttributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attributes[NSFontAttributeName];
XCTAssertGreaterThan(font.pointSize, eventFormatter.defaultTextFont.pointSize, @"H3 tags should be included and be larger than the default body size.");
XCTAssertLessThanOrEqual(font.pointSize, maxHeaderSize, @"H3 tags shouldn't exceed the max header size.");
}];
}
- (void)testRenderHTMLStringWithPreCode
{
NSString *html = @"<pre><code>1\n2\n3\n4\n</code></pre>";
NSAttributedString *as = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
NSString *a = as.string;
// \R : any newlines
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\R" options:0 error:0];
XCTAssertEqual(3, [regex numberOfMatchesInString:a options:0 range:NSMakeRange(0, a.length)], "renderHTMLString must keep line break in <pre> and <code> blocks");
[as enumerateAttributesInRange:NSMakeRange(0, as.length) options:(0) usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attrs[NSFontAttributeName];
XCTAssertEqualObjects(font.fontName, @"Menlo-Regular", "The font for <pre> and <code> should be monospace");
}];
}
- (void)testRenderHTMLStringWithLink
{
// Given an HTML string with a link inside of it.
NSString *html = @"This text contains a <a href=\"https://www.matrix.org/\">link</a>.";
// When rendering this string as an attributed string.
NSAttributedString *attributedString = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
// Then the attributed string should contain all of the text,
XCTAssertEqualObjects(attributedString.string, @"This text contains a link.", @"The text should be preserved when adding a link.");
// and the link should be added as an attachment.
__block BOOL didFindLink = NO;
[attributedString enumerateAttribute:NSLinkAttributeName
inRange:NSMakeRange(0, attributedString.length)
options:0
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
if ([value isKindOfClass:NSURL.class])
{
NSURL *url = (NSURL *)value;
XCTAssertEqualObjects(url, [NSURL URLWithString:@"https://www.matrix.org/"], @"href links should be included in the text.");
didFindLink = YES;
}
}];
XCTAssertTrue(didFindLink, @"There should be a link in the attributed string.");
}
- (void)testRenderHTMLStringWithLinkInHeader
{
// Given HTML strings with links contained within h1/h2 tags.
NSString *h1HTML = @"<h1><a href=\"https://www.matrix.org/\">Matrix.org</a></h1>";
NSString *h3HTML = @"<h3><a href=\"https://www.matrix.org/\">Matrix.org</a></h3>";
// When rendering these strings as attributed strings.
NSAttributedString *h1AttributedString = [eventFormatter renderHTMLString:h1HTML forEvent:anEvent withRoomState:nil];
NSAttributedString *h3AttributedString = [eventFormatter renderHTMLString:h3HTML forEvent:anEvent withRoomState:nil];
// Then the attributed string should contain all of the text,
XCTAssertEqualObjects(h1AttributedString.string, @"Matrix.org", @"The text from an H1 tag should be preserved when removing formatting.");
XCTAssertEqualObjects(h3AttributedString.string, @"Matrix.org", @"The text from an H3 tag should not change.");
// and be formatted as a header with the link added as an attachment.
__block BOOL didFindH1Link = NO;
[h1AttributedString enumerateAttributesInRange:NSMakeRange(0, h1AttributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attributes[NSFontAttributeName];
NSURL *url = attributes[NSLinkAttributeName];
if (font)
{
XCTAssertGreaterThan(font.pointSize, eventFormatter.defaultTextFont.pointSize, @"H1 tags should be larger than the default body size.");
XCTAssertLessThanOrEqual(font.pointSize, maxHeaderSize, @"H1 tags shouldn't exceed the max header size.");
}
if (url)
{
XCTAssertEqualObjects(url, [NSURL URLWithString:@"https://www.matrix.org/"], @"href links should be included in the text.");
didFindH1Link = YES;
}
}];
__block BOOL didFindH3Link = NO;
[h3AttributedString enumerateAttributesInRange:NSMakeRange(0, h3AttributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = attributes[NSFontAttributeName];
NSURL *url = attributes[NSLinkAttributeName];
if (font)
{
XCTAssertGreaterThan(font.pointSize, eventFormatter.defaultTextFont.pointSize, @"H3 tags should be included and be larger than the default.");
}
if (url)
{
XCTAssertEqualObjects(url, [NSURL URLWithString:@"https://www.matrix.org/"], @"href links should be included in the text.");
didFindH3Link = YES;
}
}];
XCTAssertTrue(didFindH1Link, @"There should be a link in the sanitised attributed string.");
XCTAssertTrue(didFindH3Link, @"There should be a link in the attributed string.");
}
- (void)testRenderHTMLStringWithIFrame
{
// Given an HTML string containing an unsupported iframe.
NSString *html = @"<iframe src=\"https://www.matrix.org/\"></iframe>";
// When rendering this string as an attributed string.
NSAttributedString *attributedString = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
// Then the attributed string should have the iframe stripped and not include any attachments.
BOOL hasAttachment = [attributedString containsAttachmentsInRange:NSMakeRange(0, attributedString.length)];
XCTAssertFalse(hasAttachment, @"iFrame attachments should be removed as they're not included in the allowedHTMLTags array.");
}
- (void)testRenderHTMLStringWithMXReply
{
// Given an HTML string representing a matrix reply.
NSString *html = @"<mx-reply><blockquote><a href=\"https://matrix.to/#/someroom/someevent\">In reply to</a> <a href=\"https://matrix.to/#/@alice:matrix.org\">@alice:matrix.org</a><br>Original message.</blockquote></mx-reply>This is a reply.";
// When rendering this string as an attributed string.
NSAttributedString *attributedString = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
// Then the attributed string should contain all of the text,
NSString *plainString = [attributedString.string stringByReplacingOccurrencesOfString:@"\U00002028" withString:@"\n"];
XCTAssertEqualObjects(plainString, @"In reply to @alice:matrix.org\nOriginal message.\nThis is a reply.",
@"The reply string should include who the original message was from, what they said, and the reply itself.");
// and format the author and original message inside of a quotation block.
__block BOOL didTestReplyText = NO;
__block BOOL didTestQuoteBlock = NO;
[attributedString enumerateAttributesInRange:NSMakeRange(0, attributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
NSString *substring = [attributedString attributedSubstringFromRange:range].string;
if ([substring isEqualToString:@"This is a reply."])
{
XCTAssertNil(attributes[DTTextBlocksAttribute], @"The reply text should not appear within a block");
didTestReplyText = YES;
}
else
{
XCTAssertNotNil(attributes[DTTextBlocksAttribute], @"The rest of the string should be within a block");
didTestQuoteBlock = YES;
}
}];
XCTAssertTrue(didTestReplyText && didTestQuoteBlock, @"Both a quote and a reply should be in the attributed string.");
}
- (void)testRenderHTMLStringWithMXReplyQuotingInvalidMessage
{
// Given an HTML string representing a matrix reply where the original message has invalid HTML.
NSString *html = @"<mx-reply><blockquote><a href=\"https://matrix.to/#/someroom/someevent\">In reply to</a> <a href=\"https://matrix.to/#/@alice:matrix.org\">@alice:matrix.org</a><br><h1>Heading with <badtag>invalid</badtag> content</h1></blockquote></mx-reply>This is a reply.";
// When rendering this string as an attributed string.
NSAttributedString *attributedString = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
// Then the attributed string should contain all of the text,
NSString *plainString = [attributedString.string stringByReplacingOccurrencesOfString:@"\U00002028" withString:@"\n"];
XCTAssertEqualObjects(plainString, @"In reply to @alice:matrix.org\nHeading with invalid content\nThis is a reply.",
@"The reply string should include who the original message was from, what they said, and the reply itself.");
// and format the author and original message inside of a quotation block. This check
// is to catch any incorrectness in the sanitizing where the original message becomes
// indented but is missing the block quote mark attribute.
__block BOOL didTestReplyText = NO;
__block BOOL didTestQuoteBlock = NO;
[attributedString enumerateAttributesInRange:NSMakeRange(0, attributedString.length)
options:0
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attributes, NSRange range, BOOL * _Nonnull stop) {
NSString *substring = [attributedString attributedSubstringFromRange:range].string;
if ([substring isEqualToString:@"This is a reply."])
{
XCTAssertNil(attributes[DTTextBlocksAttribute], @"The reply text should not appear within a block");
didTestReplyText = YES;
}
else
{
XCTAssertNotNil(attributes[DTTextBlocksAttribute], @"The rest of the string should be within a block");
XCTAssertNotNil(attributes[kMXKToolsBlockquoteMarkAttribute], @"The block should have the blockquote style applied");
didTestQuoteBlock = YES;
}
}];
XCTAssertTrue(didTestReplyText && didTestQuoteBlock, @"Both a quote and a reply should be in the attributed string.");
}
- (void)testRenderHTMLStringWithImageHandler
{
MXWeakify(self);
// Given an HTML string that contains an image tag inline.
NSURL *imageURL = [NSURL URLWithString:@"https://matrix.org/images/matrix-logo.svg"];
NSString *html = [NSString stringWithFormat:@"Look at this logo: <img src=\"%@\"> Very nice.", imageURL.absoluteString];
// When rendering this string as an attributed string using an appropriate image handler block.
eventFormatter.allowedHTMLTags = [eventFormatter.allowedHTMLTags arrayByAddingObject:@"img"];
eventFormatter.htmlImageHandler = ^NSURL *(NSString *sourceURL, CGFloat width, CGFloat height) {
MXStrongifyAndReturnValueIfNil(self, nil);
// Replace the image URL with one from the tests bundle
NSBundle *bundle = [NSBundle bundleForClass:self.class];;
return [bundle URLForResource:@"test" withExtension:@"png"];
};
NSAttributedString *attributedString = [eventFormatter renderHTMLString:html forEvent:anEvent withRoomState:nil];
// Then the attributed string should contain all of the text,
NSString *plainString = [attributedString.string stringByReplacingOccurrencesOfString:@"\U0000fffc" withString:@""];
XCTAssertEqualObjects(plainString, @"Look at this logo: Very nice.", @"The string should include the original text.");
// and have the image included as an attachment.
__block BOOL hasImageAttachment = NO;
[attributedString enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, attributedString.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if ([value image])
{
hasImageAttachment = YES;
}
}];
XCTAssertTrue(hasImageAttachment, @"There should be an attachment that contains the image.");
}
- (void)testMarkdownFormatting
{
NSString *html = [eventFormatter htmlStringFromMarkdownString:@"Line One.\nLine Two."];
BOOL hardBreakExists = [html rangeOfString:@"<br />"].location != NSNotFound;
BOOL openParagraphExists = [html rangeOfString:@"<p>"].location != NSNotFound;
BOOL closeParagraphExists = [html rangeOfString:@"</p>"].location != NSNotFound;
// Check for some known error cases
XCTAssert(hardBreakExists, "The soft break (\\n) must be converted to a hard break (<br />).");
XCTAssert(!openParagraphExists && !closeParagraphExists, "The html must not contain any opening or closing paragraph tags.");
}
#pragma mark - Links
- (void)testRoomAliasLink
{
NSString *s = @"Matrix HQ room is at #matrix:matrix.org.";
NSAttributedString *as = [eventFormatter renderString:s forEvent:anEvent];
NSRange linkRange = [s rangeOfString:@"#matrix:matrix.org"];
__block NSUInteger ranges = 0;
__block BOOL linkCreated = NO;
[as enumerateAttributesInRange:NSMakeRange(0, as.length) options:(0) usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
ranges++;
if (NSEqualRanges(linkRange, range))
{
linkCreated = (attrs[NSLinkAttributeName] != nil);
}
}];
XCTAssertEqual(ranges, 3, @"A sub-component must have been found");
XCTAssert(linkCreated, @"Link not created as expected: %@", as);
}
- (void)testLinkWithRoomAliasLink
{
NSString *s = @"Matrix HQ room is at https://matrix.to/#/room/#matrix:matrix.org.";
NSAttributedString *as = [eventFormatter renderString:s forEvent:anEvent];
__block NSUInteger ranges = 0;
[as enumerateAttributesInRange:NSMakeRange(0, as.length) options:(0) usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
ranges++;
}];
XCTAssertEqual(ranges, 1, @"There should be no link in this case. We let the UI manage the link");
}
#pragma mark - Event sender/target info
- (void)testUserDisplayNameFromEventContent {
MXEvent *event = [self eventFromJSON:@"{\"sender\":\"@alice:matrix.org\",\"content\":{\"displayname\":\"bob\",\"membership\":\"invite\"},\"origin_server_ts\":1616488993287,\"state_key\":\"@bob:matrix.org\",\"room_id\":\"!foofoofoofoofoofoo:matrix.org\",\"event_id\":\"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do\",\"type\":\"m.room.member\",\"unsigned\":{}}"];
XCTAssertEqualObjects([eventFormatter userDisplayNameFromContentInEvent:event withMembershipFilter:nil], @"bob");
XCTAssertEqualObjects([eventFormatter userDisplayNameFromContentInEvent:event withMembershipFilter:@"invite"], @"bob");
XCTAssertEqualObjects([eventFormatter userDisplayNameFromContentInEvent:event withMembershipFilter:@"join"], nil);
}
- (void)testUserDisplayNameFromNonMembershipEventContent {
MXEvent *event = [self eventFromJSON:@"{\"sender\":\"@alice:matrix.org\",\"content\":{\"ciphertext\":\"foo\",\"sender_key\":\"bar\",\"device_id\":\"foobar\",\"algorithm\":\"m.megolm.v1.aes-sha2\"}},\"origin_server_ts\":1616488993287,\"state_key\":\"@bob:matrix.org\",\"room_id\":\"!foofoofoofoofoofoo:matrix.org\",\"event_id\":\"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do\",\"type\":\"m.room.encrypted\",\"unsigned\":{}}"];
XCTAssertEqualObjects([eventFormatter userDisplayNameFromContentInEvent:event withMembershipFilter:nil], nil);
XCTAssertEqualObjects([eventFormatter userDisplayNameFromContentInEvent:event withMembershipFilter:@"join"], nil);
}
- (void)testUserAvatarUrlFromEventContent {
MXEvent *event = [self eventFromJSON:@"{\"sender\":\"@alice:matrix.org\",\"content\":{\"displayname\":\"bob\",\"avatar_url\":\"mxc://foo.bar\",\"membership\":\"join\"},\"origin_server_ts\":1616488993287,\"state_key\":\"@bob:matrix.org\",\"room_id\":\"!foofoofoofoofoofoo:matrix.org\",\"event_id\":\"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do\",\"type\":\"m.room.member\",\"unsigned\":{}}"];
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:nil], @"mxc://foo.bar");
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:@"invite"], nil);
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:@"join"], @"mxc://foo.bar");
}
- (void)testUserAvatarUrlFromEventWithNonMXCAvatarUrlContent {
MXEvent *event = [self eventFromJSON:@"{\"sender\":\"@alice:matrix.org\",\"content\":{\"displayname\":\"bob\",\"avatar_url\":\"http://foo.bar\",\"membership\":\"join\"},\"origin_server_ts\":1616488993287,\"state_key\":\"@bob:matrix.org\",\"room_id\":\"!foofoofoofoofoofoo:matrix.org\",\"event_id\":\"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do\",\"type\":\"m.room.member\",\"unsigned\":{}}"];
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:nil], nil);
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:@"invite"], nil);
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:@"join"], nil);
}
- (void)testUserAvatarUrlFromNonMembershipEventContent {
MXEvent *event = [self eventFromJSON:@"{\"sender\":\"@alice:matrix.org\",\"content\":{\"ciphertext\":\"foo\",\"sender_key\":\"bar\",\"device_id\":\"foobar\",\"algorithm\":\"m.megolm.v1.aes-sha2\"}},\"origin_server_ts\":1616488993287,\"state_key\":\"@bob:matrix.org\",\"room_id\":\"!foofoofoofoofoofoo:matrix.org\",\"event_id\":\"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do\",\"type\":\"m.room.encrypted\",\"unsigned\":{}}"];
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:nil], nil);
XCTAssertEqualObjects([eventFormatter userAvatarUrlFromContentInEvent:event withMembershipFilter:@"join"], nil);
}
- (MXEvent *)eventFromJSON:(NSString *)json {
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
return [MXEvent modelFromJSON:dict];
}
@end

View File

@@ -0,0 +1,27 @@
/*
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 "MXKRoomDataSource.h"
@interface MXKRoomDataSource (Tests)
- (NSArray<id<MXKRoomBubbleCellDataStoring>> *)getBubbles;
- (void)replaceBubbles:(NSArray<id<MXKRoomBubbleCellDataStoring>> *)newBubbles;
- (void)queueEventForProcessing:(MXEvent*)event withRoomState:(MXRoomState*)roomState direction:(MXTimelineDirection)direction;
- (void)processQueuedEvents:(void (^)(NSUInteger addedHistoryCellNb, NSUInteger addedLiveCellNb))onComplete;
@end

View File

@@ -0,0 +1,29 @@
/*
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 "MXKRoomDataSource+Tests.h"
@implementation MXKRoomDataSource (Tests)
- (NSArray<id<MXKRoomBubbleCellDataStoring>> *)getBubbles {
return bubbles;
}
- (void)replaceBubbles:(NSArray<id<MXKRoomBubbleCellDataStoring>> *)newBubbles {
bubbles = [NSMutableArray arrayWithArray:newBubbles];
}
@end

View File

@@ -0,0 +1,155 @@
/*
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 Foundation
import XCTest
@testable import MatrixKit
class MXKRoomDataSourceTests: XCTestCase {
// MARK: - Destruction tests
func testDestroyRemovesAllBubbles() {
let dataSource = StubMXKRoomDataSource()
dataSource.destroy()
XCTAssert(dataSource.getBubbles()?.isEmpty != false)
}
func testDestroyDeallocatesAllBubbles() throws {
let dataSource = StubMXKRoomDataSource()
weak var first = try XCTUnwrap(dataSource.getBubbles()?.first)
weak var last = try XCTUnwrap(dataSource.getBubbles()?.last)
dataSource.destroy()
XCTAssertNil(first)
XCTAssertNil(last)
}
// MARK: - Collapsing tests
func testCollapseBubblesWhenProcessingTogether() throws {
let dataSource = try FakeMXKRoomDataSource.make()
try dataSource.queueEvent1()
try dataSource.queueEvent2()
awaitEventProcessing(for: dataSource)
dataSource.verifyCollapsedEvents(2)
}
func testCollapseBubblesWhenProcessingAlone() throws {
let dataSource = try FakeMXKRoomDataSource.make()
try dataSource.queueEvent1()
awaitEventProcessing(for: dataSource)
try dataSource.queueEvent2()
awaitEventProcessing(for: dataSource)
dataSource.verifyCollapsedEvents(2)
}
private func awaitEventProcessing(for dataSource: MXKRoomDataSource) {
let e = expectation(description: "The wai-ai-ting is the hardest part")
dataSource.processQueuedEvents { _, _ in
e.fulfill()
}
waitForExpectations(timeout: 2) { error in
XCTAssertNil(error)
}
}
}
// MARK: - Test doubles
private final class StubMXKRoomDataSource: MXKRoomDataSource {
override init() {
super.init()
let data1 = MXKRoomBubbleCellData()
let data2 = MXKRoomBubbleCellData()
let data3 = MXKRoomBubbleCellData()
data1.nextCollapsableCellData = data2
data2.prevCollapsableCellData = data1
data2.nextCollapsableCellData = data3
data3.prevCollapsableCellData = data2
replaceBubbles([data1, data2, data3])
}
}
private final class FakeMXKRoomDataSource: MXKRoomDataSource {
class func make() throws -> FakeMXKRoomDataSource {
let dataSource = try XCTUnwrap(FakeMXKRoomDataSource(roomId: "!foofoofoofoofoofoo:matrix.org", andMatrixSession: nil))
dataSource.registerCellDataClass(CollapsibleBubbleCellData.self, forCellIdentifier: kMXKRoomBubbleCellDataIdentifier)
dataSource.eventFormatter = CountingEventFormatter(matrixSession: nil)
return dataSource
}
override var state: MXKDataSourceState {
MXKDataSourceStateReady
}
override var roomState: MXRoomState! {
nil
}
func queueEvent1() throws {
try queueEvent(json: #"{"sender":"@alice:matrix.org","content":{"displayname":"bob","membership":"invite"},"origin_server_ts":1616488993287,"state_key":"@bob:matrix.org","room_id":"!foofoofoofoofoofoo:matrix.org","event_id":"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do","type":"m.room.member","unsigned":{"age":1204610,"prev_sender":"@alice:matrix.org","prev_content":{"membership":"leave"},"replaces_state":"$9mQ6RtscXqHCxWqOElI-eP_kwpkuPd2Czm3UHviGoyE"}}"#)
}
func queueEvent2() throws {
try queueEvent(json: #"{"sender":"@alice:matrix.org","content":{"displayname":"john","membership":"invite"},"origin_server_ts":1616488967295,"state_key":"@john:matrix.org","room_id":"!foofoofoofoofoofoo:matrix.org","event_id":"$-00slfAluxVTP2VWytgDThTmh3nLd0WJD6gzBo2scJM","type":"m.room.member","unsigned":{"age":1712006,"prev_sender":"@alice:matrix.org","prev_content":{"membership":"leave"},"replaces_state":"$NRNkCMKeKK5NtTfWkMfTlMr5Ygw60Q2CQYnJNkbzyrs"}}"#)
}
private func queueEvent(json: String) throws {
let data = try XCTUnwrap(json.data(using: .utf8))
let dict = try XCTUnwrap((try JSONSerialization.jsonObject(with: data, options: [])) as? [AnyHashable: Any])
let event = MXEvent(fromJSON: dict)
queueEvent(forProcessing: event, with: nil, direction: __MXTimelineDirectionForwards)
}
func verifyCollapsedEvents(_ number: Int) {
let message = getBubbles()?.first?.collapsedAttributedTextMessage.string
XCTAssertEqual(message, "\(number)")
}
}
private final class CollapsibleBubbleCellData: MXKRoomBubbleCellData {
override init() {
super.init()
}
required init!(event: MXEvent!, andRoomState roomState: MXRoomState!, andRoomDataSource roomDataSource: MXKRoomDataSource!) {
super.init(event: event, andRoomState: roomState, andRoomDataSource: roomDataSource)
collapsable = true
}
override func collapse(with cellData: MXKRoomBubbleCellDataStoring!) -> Bool {
true
}
}
private final class CountingEventFormatter: MXKEventFormatter {
override func attributedString(from events: [MXEvent]!, with roomState: MXRoomState!, error: UnsafeMutablePointer<MXKEventFormatterError>!) -> NSAttributedString! {
NSAttributedString(string: "\(events.count)")
}
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,99 @@
/*
Copyright 2019 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 XCTest
@testable import MatrixKit
import MobileCoreServices
class MXKUTITests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testUTIFromMimeType() {
guard let uti = MXKUTI(mimeType: "application/pdf") else {
XCTFail("uti should not be nil")
return
}
let fileExtension = uti.fileExtension?.lowercased() ?? ""
XCTAssertTrue(uti.isFile)
XCTAssertEqual(fileExtension, "pdf")
}
func testUTIFromFileExtension() {
let uti = MXKUTI(fileExtension: "pdf")
let fileExtension = uti.fileExtension?.lowercased() ?? ""
let mimeType = uti.mimeType ?? ""
XCTAssertTrue(uti.isFile)
XCTAssertEqual(fileExtension, "pdf")
XCTAssertEqual(mimeType, "application/pdf")
}
func testUTIFromLocalFileURLLoadingResourceValues() {
let bundle = Bundle(for: type(of: self))
guard let localFileURL = bundle.url(forResource: "Text", withExtension: "txt") else {
XCTFail("localFileURL should not be nil")
return
}
guard let uti = MXKUTI(localFileURL: localFileURL) else {
XCTFail("uti should not be nil")
return
}
let fileExtension = uti.fileExtension?.lowercased() ?? ""
let mimeType = uti.mimeType ?? ""
XCTAssertTrue(uti.isFile)
XCTAssertEqual(fileExtension, "txt")
XCTAssertEqual(mimeType, "text/plain")
}
func testUTIFromLocalFileURL() {
let bundle = Bundle(for: type(of: self))
guard let localFileURL = bundle.url(forResource: "Text", withExtension: "txt") else {
XCTFail("localFileURL should not be nil")
return
}
guard let uti = MXKUTI(localFileURL: localFileURL, loadResourceValues: false) else {
XCTFail("uti should not be nil")
return
}
let fileExtension = uti.fileExtension?.lowercased() ?? ""
let mimeType = uti.mimeType ?? ""
XCTAssertTrue(uti.isFile)
XCTAssertEqual(fileExtension, "txt")
XCTAssertEqual(mimeType, "text/plain")
}
}