Compare commits
3 Commits
7449688bfe
...
8f6a012538
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 8f6a012538 | |
Shadowfacts | 91d6430815 | |
Shadowfacts | eac5a4c9a6 |
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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>
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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] {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue