Compare commits

...

3 Commits

7 changed files with 98 additions and 20 deletions

View File

@ -1,13 +1,14 @@
// //
// CharacterCounterTests.swift // CharacterCounterTests.swift
// PachydermTests // ComposeUITests
// //
// Created by Shadowfacts on 9/29/18. // Created by Shadowfacts on 9/29/18.
// Copyright © 2018 Shadowfacts. All rights reserved. // Copyright © 2018 Shadowfacts. All rights reserved.
// //
import XCTest import XCTest
@testable import Pachyderm @testable import ComposeUI
import InstanceFeatures
class CharacterCounterTests: XCTestCase { class CharacterCounterTests: XCTestCase {
@ -16,32 +17,34 @@ class CharacterCounterTests: XCTestCase {
override func tearDown() { override func tearDown() {
} }
let features = InstanceFeatures()
func testCountEmpty() { func testCountEmpty() {
XCTAssertEqual(CharacterCounter.count(text: ""), 0) XCTAssertEqual(CharacterCounter.count(text: "", for: features), 0)
} }
func testCountPlainText() { func testCountPlainText() {
XCTAssertEqual(CharacterCounter.count(text: "This is an example message"), 26) XCTAssertEqual(CharacterCounter.count(text: "This is an example message", for: features), 26)
XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄"), 43) XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄", for: features), 43)
XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄"), 7) XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄", for: features), 7)
} }
func testCountLinks() { func testCountLinks() {
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://example.com"), 55) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://example.com", for: features), 55)
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link 😄: https://example.com"), 57) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link 😄: https://example.com", for: features), 57)
XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄: https://example.com"), 32) XCTAssertEqual(CharacterCounter.count(text: "😄😄😄😄😄😄😄: https://example.com", for: features), 32)
XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://a.much.longer.example.com/link?foo=bar#baz"), 55) XCTAssertEqual(CharacterCounter.count(text: "This is an example with a link: https://a.much.longer.example.com/link?foo=bar#baz", for: features), 55)
} }
func testCountLocalMentions() { func testCountLocalMentions() {
XCTAssertEqual(CharacterCounter.count(text: "hello @example"), 14) XCTAssertEqual(CharacterCounter.count(text: "hello @example", for: features), 14)
XCTAssertEqual(CharacterCounter.count(text: "@some_really_long_name"), 22) XCTAssertEqual(CharacterCounter.count(text: "@some_really_long_name", for: features), 22)
} }
func testCountRemoteMentions() { func testCountRemoteMentions() {
XCTAssertEqual(CharacterCounter.count(text: "hello @example@some.remote.social"), 14) XCTAssertEqual(CharacterCounter.count(text: "hello @example@some.remote.social", for: features), 14)
XCTAssertEqual(CharacterCounter.count(text: "hello @some_really_long_name@some-long.remote-instance.social"), 28) XCTAssertEqual(CharacterCounter.count(text: "hello @some_really_long_name@some-long.remote-instance.social", for: features), 28)
} }
} }

View File

@ -17,7 +17,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
@ -35,7 +35,7 @@
BlueprintIdentifier = "PachydermTests" BlueprintIdentifier = "PachydermTests"
BuildableName = "PachydermTests" BuildableName = "PachydermTests"
BlueprintName = "PachydermTests" BlueprintName = "PachydermTests"
ReferencedContainer = "container:Pachyderm"> ReferencedContainer = "container:">
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
@ -56,7 +56,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
</LaunchAction> </LaunchAction>
@ -72,7 +72,7 @@
BlueprintIdentifier = "D61099AA2144B0CC00432DC2" BlueprintIdentifier = "D61099AA2144B0CC00432DC2"
BuildableName = "Pachyderm.framework" BuildableName = "Pachyderm.framework"
BlueprintName = "Pachyderm" BlueprintName = "Pachyderm"
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:../../Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
</ProfileAction> </ProfileAction>

View File

@ -51,8 +51,12 @@ public final class Status: StatusProtocol, Decodable, Sendable {
do { do {
self.url = try container.decodeIfPresent(WebURL.self, forKey: .url) self.url = try container.decodeIfPresent(WebURL.self, forKey: .url)
} catch { } catch {
let s = (try? container.decode(String.self, forKey: .url)) ?? "<failed to decode string>" let s = try? container.decode(String.self, forKey: .url)
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "Could not decode URL '\(s)', reason: \(String(describing: error))") if s == "" {
self.url = nil
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "<failed to decode string>")'", underlyingError: error))
}
} }
self.account = try container.decode(Account.self, forKey: .account) self.account = try container.decode(Account.self, forKey: .account)
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID) self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)

View File

@ -8,6 +8,7 @@
import XCTest import XCTest
@testable import Pachyderm @testable import Pachyderm
@MainActor
class NotificationGroupTests: XCTestCase { class NotificationGroupTests: XCTestCase {
let decoder: JSONDecoder = { let decoder: JSONDecoder = {

View File

@ -0,0 +1,53 @@
//
// StatusTests.swift
//
//
// Created by Shadowfacts on 5/8/23.
//
import XCTest
@testable import Pachyderm
final class StatusTests: XCTestCase {
func testDecode() {
let data = """
{
"id": "1",
"uri": "https://example.com/a/1",
"url": "",
"account": {
"id": "2",
"username": "a",
"acct": "a",
"display_name": "",
"locked": false,
"created_at": 0,
"followers_count": 0,
"following_count": 0,
"statuses_count": 0,
"note": "",
"url": "https://example.com/a"
},
"content": "",
"created_at": 0,
"emojis": [],
"reblogs_count": 0,
"favourites_count": 0,
"sensitive": false,
"spoiler_text": "",
"visibility": "public",
"media_attachments": [],
"mentions": [],
"tags": []
}
""".data(using: .utf8)!
do {
_ = try JSONDecoder().decode(Status.self, from: data)
} catch {
print(error)
XCTFail()
}
}
}

View File

@ -355,8 +355,24 @@ extension NotificationsCollectionViewController {
} }
func handlePrependItems(_ timelineItems: [NotificationGroup]) async { func handlePrependItems(_ timelineItems: [NotificationGroup]) async {
let topItem = dataSource.snapshot().itemIdentifiers(inSection: .notifications).first
// we always replace all, because new items are merged with existing ones // we always replace all, because new items are merged with existing ones
await handleReplaceAllItems(timelineItems) await handleReplaceAllItems(timelineItems)
// preserve the scroll position
// todo: this won't work for cmd+r when not at top
if let topID = topItem?.group?.notifications.first?.id {
// the exact item may have changed, due to merging
let newTopGroup = timelineItems.first {
$0.notifications.contains {
$0.id == topID
}
}!
if let newTopIndexPath = dataSource.indexPath(for: .group(newTopGroup)) {
collectionView.scrollToItem(at: newTopIndexPath, at: .top, animated: false)
}
}
} }
func loadOlder() async throws -> [NotificationGroup] { func loadOlder() async throws -> [NotificationGroup] {

View File

@ -153,6 +153,7 @@ extension TimelineLikeCollectionViewController {
} }
await apply(snapshot, animatingDifferences: false) await apply(snapshot, animatingDifferences: false)
// todo: this won't work for cmd+r when not at top
if let first, if let first,
let indexPath = dataSource.indexPath(for: first) { let indexPath = dataSource.indexPath(for: first) {
// TODO: i can't tell if this actually works or not // TODO: i can't tell if this actually works or not