Merge branch 'ios-candidate' of https://github.com/Ranchero-Software/NetNewsWire into ios-candidate
This commit is contained in:
commit
3a69425752
|
@ -111,6 +111,9 @@
|
|||
9E44C90F23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E44C90E23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift */; };
|
||||
9E5ABE9A236BE6BD00B5DE9F /* feedly-1-initial in Resources */ = {isa = PBXBuildFile; fileRef = 9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */; };
|
||||
9E5DE60E23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */; };
|
||||
9E5EC15923E01D8A00A4E503 /* FeedlyCollectionParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5EC15823E01D8A00A4E503 /* FeedlyCollectionParser.swift */; };
|
||||
9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5EC15A23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift */; };
|
||||
9E5EC15D23E0D58500A4E503 /* FeedlyFeedParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5EC15C23E0D58500A4E503 /* FeedlyFeedParser.swift */; };
|
||||
9E672394236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */; };
|
||||
9E672396236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */; };
|
||||
9E7299D723505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */; };
|
||||
|
@ -164,6 +167,10 @@
|
|||
9EF1B10723590D61000A486A /* FeedlyGetStreamIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */; };
|
||||
9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */; };
|
||||
9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */; };
|
||||
9EF58EB023E1606000992A2B /* FeedlyTextSanitizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF58EAF23E1606000992A2B /* FeedlyTextSanitizationTests.swift */; };
|
||||
9EF58EB223E1647400992A2B /* FeedlyCollectionParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF58EB123E1647400992A2B /* FeedlyCollectionParserTests.swift */; };
|
||||
9EF58EB423E1655300992A2B /* FeedlyFeedParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF58EB323E1655300992A2B /* FeedlyFeedParserTests.swift */; };
|
||||
9EF58EB623E1669F00992A2B /* FeedlyEntryParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF58EB523E1669F00992A2B /* FeedlyEntryParserTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -327,6 +334,9 @@
|
|||
9E489E92236101FC004372EE /* FeedlyUpdateAccountFeedsWithItemsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "feedly-1-initial"; sourceTree = "<group>"; };
|
||||
9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFetchIdsForMissingArticlesOperation.swift; sourceTree = "<group>"; };
|
||||
9E5EC15823E01D8A00A4E503 /* FeedlyCollectionParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollectionParser.swift; sourceTree = "<group>"; };
|
||||
9E5EC15A23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRTLTextSanitizer.swift; sourceTree = "<group>"; };
|
||||
9E5EC15C23E0D58500A4E503 /* FeedlyFeedParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeedParser.swift; sourceTree = "<group>"; };
|
||||
9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshAccessTokenOperation.swift; sourceTree = "<group>"; };
|
||||
9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAcessTokenRefreshing.swift; sourceTree = "<group>"; };
|
||||
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAddFeedToCollectionOperation.swift; sourceTree = "<group>"; };
|
||||
|
@ -386,6 +396,10 @@
|
|||
9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStreamIds.swift; sourceTree = "<group>"; };
|
||||
9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetUpdatedArticleIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9EF58EAF23E1606000992A2B /* FeedlyTextSanitizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyTextSanitizationTests.swift; sourceTree = "<group>"; };
|
||||
9EF58EB123E1647400992A2B /* FeedlyCollectionParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollectionParserTests.swift; sourceTree = "<group>"; };
|
||||
9EF58EB323E1655300992A2B /* FeedlyFeedParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeedParserTests.swift; sourceTree = "<group>"; };
|
||||
9EF58EB523E1669F00992A2B /* FeedlyEntryParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyEntryParserTests.swift; sourceTree = "<group>"; };
|
||||
D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = "<group>"; };
|
||||
D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = "<group>"; };
|
||||
D511EEB7202422BB00712EC3 /* Account_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_release.xcconfig; sourceTree = "<group>"; };
|
||||
|
@ -652,6 +666,10 @@
|
|||
9E0260CA236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift */,
|
||||
9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */,
|
||||
9EA643D823945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift */,
|
||||
9EF58EAF23E1606000992A2B /* FeedlyTextSanitizationTests.swift */,
|
||||
9EF58EB123E1647400992A2B /* FeedlyCollectionParserTests.swift */,
|
||||
9EF58EB323E1655300992A2B /* FeedlyFeedParserTests.swift */,
|
||||
9EF58EB523E1669F00992A2B /* FeedlyEntryParserTests.swift */,
|
||||
9E79F7732395C9EF0031DB98 /* feedly-add-new-feed */,
|
||||
9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */,
|
||||
9EC804E4236C1A7F0057CFCB /* feedly-2-changestatuses */,
|
||||
|
@ -720,7 +738,9 @@
|
|||
children = (
|
||||
9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */,
|
||||
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */,
|
||||
9E5EC15823E01D8A00A4E503 /* FeedlyCollectionParser.swift */,
|
||||
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */,
|
||||
9E5EC15C23E0D58500A4E503 /* FeedlyFeedParser.swift */,
|
||||
9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */,
|
||||
9E1773D4234570E30056A5A8 /* FeedlyEntryParser.swift */,
|
||||
9EAEC625233318400085D7C9 /* FeedlyStream.swift */,
|
||||
|
@ -731,6 +751,7 @@
|
|||
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */,
|
||||
9EA643D4239306AC0018A28C /* FeedlyFeedsSearchResponse.swift */,
|
||||
9EBD49C123C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift */,
|
||||
9E5EC15A23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -977,10 +998,12 @@
|
|||
51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */,
|
||||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
||||
9EA643D3239305680018A28C /* FeedlySearchOperation.swift in Sources */,
|
||||
9E5EC15D23E0D58500A4E503 /* FeedlyFeedParser.swift in Sources */,
|
||||
9E1D15532334304B00F4944C /* FeedlyGetStreamContentsOperation.swift in Sources */,
|
||||
9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */,
|
||||
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
|
||||
9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */,
|
||||
511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */,
|
||||
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||
|
@ -995,6 +1018,7 @@
|
|||
9EEAE06E235D002D00E3FEE4 /* FeedlyGetCollectionsService.swift in Sources */,
|
||||
5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */,
|
||||
9E85C8ED2367020700D0F1F7 /* FeedlyGetEntriesService.swift in Sources */,
|
||||
9E5EC15923E01D8A00A4E503 /* FeedlyCollectionParser.swift in Sources */,
|
||||
9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */,
|
||||
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */,
|
||||
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */,
|
||||
|
@ -1081,6 +1105,7 @@
|
|||
9EC228572362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift in Sources */,
|
||||
9E03C122235E62E100FB6D9E /* FeedlyTestSupport.swift in Sources */,
|
||||
9EAADA1023C93144003A801F /* TestGetEntriesService.swift in Sources */,
|
||||
9EF58EB423E1655300992A2B /* FeedlyFeedParserTests.swift in Sources */,
|
||||
9E1FF8622368219B00834C24 /* TestGetPagedStreamIdsService.swift in Sources */,
|
||||
9E03C11E235D976500FB6D9E /* FeedlyGetCollectionsOperationTests.swift in Sources */,
|
||||
9E85C8E62366FED600D0F1F7 /* TestGetStreamContentsService.swift in Sources */,
|
||||
|
@ -1088,14 +1113,17 @@
|
|||
9E03C11C235D921400FB6D9E /* FeedlyOperationTests.swift in Sources */,
|
||||
9E1FF8602368216B00834C24 /* TestGetStreamIdsService.swift in Sources */,
|
||||
9E85C8E82366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift in Sources */,
|
||||
9EF58EB023E1606000992A2B /* FeedlyTextSanitizationTests.swift in Sources */,
|
||||
5165D7122282080C00D9D53D /* AccountFeedbinFolderContentsSyncTest.swift in Sources */,
|
||||
51D5875E227F643C00900287 /* AccountFeedbinFolderSyncTest.swift in Sources */,
|
||||
9EF58EB623E1669F00992A2B /* FeedlyEntryParserTests.swift in Sources */,
|
||||
9EC804E3236C18AB0057CFCB /* FeedlySyncAllMockResponseProvider.swift in Sources */,
|
||||
9E1FF8682368EE4900834C24 /* TestGetCollectionsService.swift in Sources */,
|
||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */,
|
||||
513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */,
|
||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */,
|
||||
9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */,
|
||||
9EF58EB223E1647400992A2B /* FeedlyCollectionParserTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// FeedlyCollectionParserTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 29/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlyCollectionParserTests: XCTestCase {
|
||||
|
||||
func testParsing() {
|
||||
let collection = FeedlyCollection(feeds: [], label: "Test Collection", id: "test/collection/1")
|
||||
let parser = FeedlyCollectionParser(collection: collection)
|
||||
XCTAssertEqual(parser.folderName, collection.label)
|
||||
XCTAssertEqual(parser.externalID, collection.id)
|
||||
}
|
||||
|
||||
func testSanitization() {
|
||||
let name = "Test Collection"
|
||||
let collection = FeedlyCollection(feeds: [], label: "<div style=\"direction:rtl;text-align:right\">\(name)</div>", id: "test/collection/1")
|
||||
let parser = FeedlyCollectionParser(collection: collection)
|
||||
XCTAssertEqual(parser.folderName, name)
|
||||
XCTAssertEqual(parser.externalID, collection.id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// FeedlyEntryParserTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 29/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlyEntryParserTests: XCTestCase {
|
||||
|
||||
func testParsing() {
|
||||
let content = FeedlyEntry.Content(content: "Test Content", direction: .leftToRight)
|
||||
let summary = FeedlyEntry.Content(content: "Test Summary", direction: .leftToRight)
|
||||
let origin = FeedlyOrigin(title: "Test Feed", streamId: "tests://feeds/1", htmlUrl: nil)
|
||||
let canonicalLink = FeedlyLink(href: "tests://feeds/1/entries/1", type: "text/html")
|
||||
let tags = [
|
||||
FeedlyTag(id: "tests/tags/1", label: "Tag 1"),
|
||||
FeedlyTag(id: "tests/tags/2", label: "Tag 2")
|
||||
]
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "Test Entry 1",
|
||||
content: content,
|
||||
summary: summary,
|
||||
author: "Bob Alice",
|
||||
crawled: .distantPast,
|
||||
recrawled: Date(timeIntervalSinceReferenceDate: 0),
|
||||
origin: origin,
|
||||
canonical: [canonicalLink],
|
||||
alternate: nil,
|
||||
unread: false,
|
||||
tags: tags,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
XCTAssertEqual(parser.id, entry.id)
|
||||
XCTAssertEqual(parser.feedUrl, origin.streamId)
|
||||
XCTAssertEqual(parser.externalUrl, canonicalLink.href)
|
||||
XCTAssertEqual(parser.title, entry.title)
|
||||
XCTAssertEqual(parser.contentHMTL, content.content)
|
||||
XCTAssertEqual(parser.summary, summary.content)
|
||||
XCTAssertEqual(parser.datePublished, .distantPast)
|
||||
XCTAssertEqual(parser.dateModified, Date(timeIntervalSinceReferenceDate: 0))
|
||||
|
||||
guard let item = parser.parsedItemRepresentation else {
|
||||
XCTFail("Expected a parsed item representation.")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(item.syncServiceID, entry.id)
|
||||
XCTAssertEqual(item.uniqueID, entry.id)
|
||||
|
||||
// The following is not an error.
|
||||
// The feedURL must match the webFeedID for the article to be connected to its matching feed.
|
||||
XCTAssertEqual(item.feedURL, origin.streamId)
|
||||
XCTAssertEqual(item.title, entry.title)
|
||||
XCTAssertEqual(item.contentHTML, content.content)
|
||||
XCTAssertEqual(item.contentText, nil, "Is it now free of HTML characters?")
|
||||
XCTAssertEqual(item.summary, summary.content)
|
||||
XCTAssertEqual(item.datePublished, entry.crawled)
|
||||
XCTAssertEqual(item.dateModified, entry.recrawled)
|
||||
|
||||
let expectedTags = Set(tags.compactMap { $0.label })
|
||||
XCTAssertEqual(item.tags, expectedTags)
|
||||
|
||||
let expectedAuthors = Set([entry.author])
|
||||
let calculatedAuthors = Set(item.authors?.compactMap { $0.name } ?? [])
|
||||
XCTAssertEqual(calculatedAuthors, expectedAuthors)
|
||||
}
|
||||
|
||||
func testSanitization() {
|
||||
let content = FeedlyEntry.Content(content: "<div style=\"direction:rtl;text-align:right\">Test Content</div>", direction: .rightToLeft)
|
||||
let summaryContent = "Test Summary"
|
||||
let summary = FeedlyEntry.Content(content: "<div style=\"direction:rtl;text-align:right\">\(summaryContent)</div>", direction: .rightToLeft)
|
||||
let origin = FeedlyOrigin(title: "Test Feed", streamId: "tests://feeds/1", htmlUrl: nil)
|
||||
let title = "Test Entry 1"
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "<div style=\"direction:rtl;text-align:right\">\(title)</div>",
|
||||
content: content,
|
||||
summary: summary,
|
||||
author: nil,
|
||||
crawled: .distantPast,
|
||||
recrawled: nil,
|
||||
origin: origin,
|
||||
canonical: nil,
|
||||
alternate: nil,
|
||||
unread: false,
|
||||
tags: nil,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
// These should be sanitized
|
||||
XCTAssertEqual(parser.title, title)
|
||||
XCTAssertEqual(parser.summary, summaryContent)
|
||||
|
||||
// These should not be sanitized because it is supposed to be HTML content.
|
||||
XCTAssertEqual(parser.contentHMTL, content.content)
|
||||
}
|
||||
|
||||
func testLocatesCanonicalExternalUrl() {
|
||||
let canonicalLink = FeedlyLink(href: "tests://feeds/1/entries/1", type: "text/html")
|
||||
let alternateLink = FeedlyLink(href: "tests://feeds/1/entries/alternate/1", type: "text/html")
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "Test Entry 1",
|
||||
content: nil,
|
||||
summary: nil,
|
||||
author: nil,
|
||||
crawled: .distantPast,
|
||||
recrawled: Date(timeIntervalSinceReferenceDate: 0),
|
||||
origin: nil,
|
||||
canonical: [canonicalLink],
|
||||
alternate: [alternateLink],
|
||||
unread: false,
|
||||
tags: nil,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
XCTAssertEqual(parser.externalUrl, canonicalLink.href)
|
||||
}
|
||||
|
||||
func testLocatesAlternateExternalUrl() {
|
||||
let canonicalLink = FeedlyLink(href: "tests://feeds/1/entries/1", type: "text/json")
|
||||
let alternateLink = FeedlyLink(href: "tests://feeds/1/entries/alternate/1", type: nil)
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "Test Entry 1",
|
||||
content: nil,
|
||||
summary: nil,
|
||||
author: nil,
|
||||
crawled: .distantPast,
|
||||
recrawled: Date(timeIntervalSinceReferenceDate: 0),
|
||||
origin: nil,
|
||||
canonical: [canonicalLink],
|
||||
alternate: [alternateLink],
|
||||
unread: false,
|
||||
tags: nil,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
XCTAssertEqual(parser.externalUrl, alternateLink.href)
|
||||
}
|
||||
|
||||
func testContentPreferredToSummary() {
|
||||
let content = FeedlyEntry.Content(content: "Test Content", direction: .leftToRight)
|
||||
let summary = FeedlyEntry.Content(content: "Test Summary", direction: .leftToRight)
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "Test Entry 1",
|
||||
content: content,
|
||||
summary: summary,
|
||||
author: nil,
|
||||
crawled: .distantPast,
|
||||
recrawled: Date(timeIntervalSinceReferenceDate: 0),
|
||||
origin: nil,
|
||||
canonical: nil,
|
||||
alternate: nil,
|
||||
unread: false,
|
||||
tags: nil,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
XCTAssertEqual(parser.contentHMTL, content.content)
|
||||
}
|
||||
|
||||
func testSummaryUsedAsContentWhenContentMissing() {
|
||||
let summary = FeedlyEntry.Content(content: "Test Summary", direction: .leftToRight)
|
||||
let entry = FeedlyEntry(id: "tests/feeds/1/entries/1",
|
||||
title: "Test Entry 1",
|
||||
content: nil,
|
||||
summary: summary,
|
||||
author: nil,
|
||||
crawled: .distantPast,
|
||||
recrawled: Date(timeIntervalSinceReferenceDate: 0),
|
||||
origin: nil,
|
||||
canonical: nil,
|
||||
alternate: nil,
|
||||
unread: false,
|
||||
tags: nil,
|
||||
categories: nil,
|
||||
enclosure: nil)
|
||||
|
||||
let parser = FeedlyEntryParser(entry: entry)
|
||||
|
||||
XCTAssertEqual(parser.contentHMTL, summary.content)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// FeedlyFeedParserTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 29/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlyFeedParserTests: XCTestCase {
|
||||
|
||||
func testParsing() {
|
||||
let name = "Test Feed"
|
||||
let website = "tests://nnw/feed/1"
|
||||
let url = "tests://nnw/feed.xml"
|
||||
let id = "feed/\(url)"
|
||||
let updated = Date.distantPast
|
||||
let feed = FeedlyFeed(id: id, title: name, updated: updated, website: website)
|
||||
let parser = FeedlyFeedParser(feed: feed)
|
||||
XCTAssertEqual(parser.title, name)
|
||||
XCTAssertEqual(parser.homePageURL, website)
|
||||
XCTAssertEqual(parser.url, url)
|
||||
XCTAssertEqual(parser.webFeedID, id)
|
||||
}
|
||||
|
||||
func testSanitization() {
|
||||
let name = "Test Feed"
|
||||
let website = "tests://nnw/feed/1"
|
||||
let url = "tests://nnw/feed.xml"
|
||||
let id = "feed/\(url)"
|
||||
let updated = Date.distantPast
|
||||
let feed = FeedlyFeed(id: id, title: "<div style=\"direction:rtl;text-align:right\">\(name)</div>", updated: updated, website: website)
|
||||
let parser = FeedlyFeedParser(feed: feed)
|
||||
XCTAssertEqual(parser.title, name)
|
||||
XCTAssertEqual(parser.homePageURL, website)
|
||||
XCTAssertEqual(parser.url, url)
|
||||
XCTAssertEqual(parser.webFeedID, id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// FeedlyTextSanitizationTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 29/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlyTextSanitizationTests: XCTestCase {
|
||||
|
||||
func testRTLSanitization() {
|
||||
|
||||
let targetsAndExpectations: [(target: String?, expectation: String?)] = [
|
||||
(nil, nil),
|
||||
("", ""),
|
||||
(" ", " "),
|
||||
("text", "text"),
|
||||
("<div style=\"direction:rtl;text-align:right\">", "<div style=\"direction:rtl;text-align:right\">"),
|
||||
("</div>", "</div>"),
|
||||
("<div style=\"direction:rtl;text-align:right\">text", "<div style=\"direction:rtl;text-align:right\">text"),
|
||||
("text</div>", "text</div>"),
|
||||
("<div style=\"direction:rtl;text-align:right\"></div>", ""),
|
||||
("<DIV style=\"direction:rtl;text-align:right\"></div>", "<DIV style=\"direction:rtl;text-align:right\"></div>"),
|
||||
("<div style=\"direction:rtl;text-align:right\"></DIV>", "<div style=\"direction:rtl;text-align:right\"></DIV>"),
|
||||
("<div style=\"direction:rtl;text-align:right\">text</div>", "text"),
|
||||
]
|
||||
|
||||
let sanitizer = FeedlyRTLTextSanitizer()
|
||||
|
||||
for (target, expectation) in targetsAndExpectations {
|
||||
let calculated = sanitizer.sanitize(target)
|
||||
XCTAssertEqual(expectation, calculated)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// FeedlyCollectionParser.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 28/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedlyCollectionParser {
|
||||
let collection: FeedlyCollection
|
||||
|
||||
private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer()
|
||||
|
||||
var folderName: String {
|
||||
return rightToLeftTextSantizer.sanitize(collection.label) ?? ""
|
||||
}
|
||||
|
||||
var externalID: String {
|
||||
return collection.id
|
||||
}
|
||||
}
|
|
@ -13,13 +13,17 @@ import RSParser
|
|||
struct FeedlyEntryParser {
|
||||
let entry: FeedlyEntry
|
||||
|
||||
private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer()
|
||||
|
||||
var id: String {
|
||||
return entry.id
|
||||
}
|
||||
|
||||
/// When ingesting articles, the feedURL must match a feed's `webFeedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
|
||||
var feedUrl: String? {
|
||||
guard let id = entry.origin?.streamId else {
|
||||
assertionFailure()
|
||||
// At this point, check Feedly's API isn't glitching or the response has not changed structure.
|
||||
assertionFailure("Entries need to be traceable to a feed or this entry will be dropped.")
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
|
@ -36,7 +40,7 @@ struct FeedlyEntryParser {
|
|||
}
|
||||
|
||||
var title: String? {
|
||||
return entry.title
|
||||
return rightToLeftTextSantizer.sanitize(entry.title)
|
||||
}
|
||||
|
||||
var contentHMTL: String? {
|
||||
|
@ -49,7 +53,7 @@ struct FeedlyEntryParser {
|
|||
}
|
||||
|
||||
var summary: String? {
|
||||
return entry.summary?.content
|
||||
return rightToLeftTextSantizer.sanitize(entry.summary?.content)
|
||||
}
|
||||
|
||||
var datePublished: Date {
|
||||
|
@ -67,6 +71,7 @@ struct FeedlyEntryParser {
|
|||
return Set([ParsedAuthor(name: name, url: nil, avatarURL: nil, emailAddress: nil)])
|
||||
}
|
||||
|
||||
/// While there is not yet a tagging interface, articles can still be searched for by tags.
|
||||
var tags: Set<String>? {
|
||||
guard let labels = entry.tags?.compactMap({ $0.label }), !labels.isEmpty else {
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// FeedlyFeedParser.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 29/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedlyFeedParser {
|
||||
let feed: FeedlyFeed
|
||||
|
||||
private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer()
|
||||
|
||||
var title: String? {
|
||||
return rightToLeftTextSantizer.sanitize(feed.title) ?? ""
|
||||
}
|
||||
|
||||
var webFeedID: String {
|
||||
return feed.id
|
||||
}
|
||||
|
||||
var url: String {
|
||||
let resource = FeedlyFeedResourceId(id: feed.id)
|
||||
return resource.url
|
||||
}
|
||||
|
||||
var homePageURL: String? {
|
||||
return feed.website
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// FeedlyRTLTextSanitizer.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 28/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedlyRTLTextSanitizer {
|
||||
private let rightToLeftPrefix = "<div style=\"direction:rtl;text-align:right\">"
|
||||
private let rightToLeftSuffix = "</div>"
|
||||
|
||||
func sanitize(_ sourceText: String?) -> String? {
|
||||
guard let source = sourceText, !source.isEmpty else {
|
||||
return sourceText
|
||||
}
|
||||
|
||||
guard source.hasPrefix(rightToLeftPrefix) && source.hasSuffix(rightToLeftSuffix) else {
|
||||
return source
|
||||
}
|
||||
|
||||
let start = source.index(source.startIndex, offsetBy: rightToLeftPrefix.indices.count)
|
||||
let end = source.index(source.endIndex, offsetBy: -rightToLeftSuffix.indices.count)
|
||||
return String(source[start..<end])
|
||||
}
|
||||
}
|
|
@ -68,9 +68,11 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
|||
}
|
||||
|
||||
// no exsiting feed, create a new one
|
||||
let id = collectionFeed.id
|
||||
let url = FeedlyFeedResourceId(id: id).url
|
||||
let feed = account.createWebFeed(with: collectionFeed.title, url: url, webFeedID: id, homePageURL: collectionFeed.website)
|
||||
let parser = FeedlyFeedParser(feed: collectionFeed)
|
||||
let feed = account.createWebFeed(with: parser.title,
|
||||
url: parser.url,
|
||||
webFeedID: parser.webFeedID,
|
||||
homePageURL: parser.homePageURL)
|
||||
|
||||
// So the same feed isn't created more than once.
|
||||
feedsAdded.insert(feed)
|
||||
|
|
|
@ -9,22 +9,17 @@
|
|||
import Foundation
|
||||
import os.log
|
||||
|
||||
protocol FeedlyCollectionsAndFoldersProviding: class {
|
||||
var collectionsAndFolders: [(FeedlyCollection, Folder)] { get }
|
||||
}
|
||||
|
||||
protocol FeedlyFeedsAndFoldersProviding {
|
||||
var feedsAndFolders: [([FeedlyFeed], Folder)] { get }
|
||||
}
|
||||
|
||||
/// Reflect Collections from Feedly as Folders.
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding, FeedlyFeedsAndFoldersProviding {
|
||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding {
|
||||
|
||||
let account: Account
|
||||
let collectionsProvider: FeedlyCollectionProviding
|
||||
let log: OSLog
|
||||
|
||||
private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]()
|
||||
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
|
||||
|
||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding, log: OSLog) {
|
||||
|
@ -41,29 +36,28 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCo
|
|||
let localFolders = account.folders ?? Set()
|
||||
let collections = collectionsProvider.collections
|
||||
|
||||
let pairs = collections.compactMap { collection -> (FeedlyCollection, Folder)? in
|
||||
guard let folder = account.ensureFolder(with: collection.label) else {
|
||||
feedsAndFolders = collections.compactMap { collection -> ([FeedlyFeed], Folder)? in
|
||||
let parser = FeedlyCollectionParser(collection: collection)
|
||||
guard let folder = account.ensureFolder(with: parser.folderName) else {
|
||||
assertionFailure("Why wasn't a folder created?")
|
||||
return nil
|
||||
}
|
||||
folder.externalID = collection.id
|
||||
return (collection, folder)
|
||||
}
|
||||
|
||||
collectionsAndFolders = pairs
|
||||
os_log(.debug, log: log, "Ensured %i folders for %i collections.", pairs.count, collections.count)
|
||||
|
||||
feedsAndFolders = pairs.map { (collection, folder) -> (([FeedlyFeed], Folder)) in
|
||||
folder.externalID = parser.externalID
|
||||
return (collection.feeds, folder)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Ensured %i folders for %i collections.", feedsAndFolders.count, collections.count)
|
||||
|
||||
// Remove folders without a corresponding collection
|
||||
let collectionFolders = Set(pairs.map { $0.1 })
|
||||
let collectionFolders = Set(feedsAndFolders.map { $0.1 })
|
||||
let foldersWithoutCollections = localFolders.subtracting(collectionFolders)
|
||||
|
||||
if !foldersWithoutCollections.isEmpty {
|
||||
for unmatched in foldersWithoutCollections {
|
||||
account.removeFolder(unmatched)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Removed %i folders: %@", foldersWithoutCollections.count, foldersWithoutCollections.map { $0.externalID ?? $0.nameForDisplay })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue