Change license to AGPL
This commit:
- Updates the `LICENSE` file
- Start every file with something like:
// Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
---
First, I removed existing license headers with this Ruby 3.1.2 script:
require 'set'
EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']
same = 0
different = 0
all_files = `git ls-files`.lines.map { |line| line.strip }
all_files.each do |relative_path|
if relative_path == 'Pods'
next
end
unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
next
end
path = File.expand_path(relative_path)
contents = File.read(path)
new_contents = contents.sub(/\/\/\n\/\/ Copyright .*\n\/\/\n\n/, '')
if contents == new_contents
same += 1
else
different += 1
end
File.write(path, new_contents)
end
puts "updated #{different} file(s), left #{same} untouched"
I'm sure this script could be improved, but it worked well enough.
Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.
Then I fixed some stragglers and updated the precommit script.
See [a similar change in the Desktop app][0].
[0]: 8bfaf598af
1243 lines
61 KiB
Swift
1243 lines
61 KiB
Swift
//
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
@testable import Signal
|
|
|
|
private extension Date {
|
|
/// Initialize a date using a compressed notation: `Date(compressedDate: 2022_04_28)`
|
|
init(compressedDate: UInt32, calendar: Calendar = .current) {
|
|
let day = Int(compressedDate % 100)
|
|
assert((1...31).contains(day))
|
|
let month = Int((compressedDate / 100) % 100)
|
|
assert((1...12).contains(month))
|
|
let year = Int(compressedDate / 1_00_00)
|
|
assert((1970...).contains(year))
|
|
self = calendar.date(from: DateComponents(year: year, month: month, day: day))!
|
|
}
|
|
}
|
|
|
|
private extension GalleryDate {
|
|
/// Initialize a GalleryDate using a compressed notation: `GalleryDate(2022_04_28)`.
|
|
///
|
|
/// Note that GalleryDates represent intervals; the above example will produce a GalleryDate for all of April 2022.
|
|
init(_ compressedDate: UInt32) {
|
|
self.init(date: Date(compressedDate: compressedDate))
|
|
}
|
|
}
|
|
|
|
private extension MediaGallerySections {
|
|
/// A functional version of the normal, inout-based API. More convenient for testing.
|
|
func trimLoadedItemsAtStart(from naiveRange: Range<Int>, relativeToSection sectionIndex: Int) -> Range<Int> {
|
|
var result = naiveRange
|
|
trimLoadedItemsAtStart(from: &result, relativeToSection: sectionIndex)
|
|
return result
|
|
}
|
|
|
|
/// A functional version of the normal, inout-based API. More convenient for testing.
|
|
func trimLoadedItemsAtEnd(from naiveRange: Range<Int>, relativeToSection sectionIndex: Int) -> Range<Int> {
|
|
var result = naiveRange
|
|
trimLoadedItemsAtEnd(from: &result, relativeToSection: sectionIndex)
|
|
return result
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private struct FakeItem: MediaGallerySectionItem, Equatable {
|
|
var uniqueId: String
|
|
var timestamp: Date
|
|
|
|
var galleryDate: GalleryDate { GalleryDate(date: timestamp) }
|
|
|
|
/// Generates an item with the given timestamp in compressed notation: `FakeItem(2022_04_28)`
|
|
///
|
|
/// The item's unique ID will be randomly generated.
|
|
init(_ compressedDate: UInt32) {
|
|
self.uniqueId = UUID().uuidString
|
|
self.timestamp = Date(compressedDate: compressedDate)
|
|
}
|
|
}
|
|
|
|
/// Takes the place of the database for MediaGallerySection tests.
|
|
private final class FakeGalleryStore: MediaGallerySectionLoader {
|
|
typealias Item = FakeItem
|
|
typealias EnumerationCompletion = MediaGalleryFinder.EnumerationCompletion
|
|
|
|
var allItems: [Item]
|
|
var itemsBySection: OrderedDictionary<GalleryDate, [Item]>
|
|
var mostRecentRequest: Range<Int> = 0..<0
|
|
|
|
/// Sorts and groups the given items for traversal as a flat array (`allItems`)
|
|
/// as well as by section (`itemsBySection`).
|
|
init(_ items: [Item]) {
|
|
self.allItems = items.sorted { $0.timestamp < $1.timestamp }
|
|
let itemsBySection = Dictionary(grouping: allItems) { $0.galleryDate }
|
|
self.itemsBySection = OrderedDictionary(keyValueMap: itemsBySection, orderedKeys: itemsBySection.keys.sorted())
|
|
}
|
|
|
|
func clone() -> Self {
|
|
return Self(self.allItems)
|
|
}
|
|
|
|
func numberOfItemsInSection(for date: GalleryDate, transaction: SDSAnyReadTransaction) -> Int {
|
|
itemsBySection[date]?.count ?? 0
|
|
}
|
|
|
|
/// Iterates over `items` and calls `block` up to `count` times.
|
|
///
|
|
/// Items with IDs matching `deletedAttachmentIds` are skipped and do not count against `count`.
|
|
/// Returns `finished` if `count` items were visited and `reachedEnd` if there were not enough items to visit.
|
|
private static func enumerate<Items>(_ items: Items, count: Int, visit: (Item) -> Void) -> EnumerationCompletion
|
|
where Items: Collection, Items.Element == Item {
|
|
items.prefix(count).forEach(visit)
|
|
if items.count < count {
|
|
return .reachedEnd
|
|
}
|
|
return .finished
|
|
}
|
|
|
|
func enumerateTimestamps(before date: Date,
|
|
count: Int,
|
|
transaction: SDSAnyReadTransaction,
|
|
block: (Date) -> Void) -> EnumerationCompletion {
|
|
// It would be more efficient to binary search here, but this is for testing.
|
|
let itemsInRange = allItems.reversed().drop { $0.timestamp >= date }
|
|
return Self.enumerate(itemsInRange, count: count) {
|
|
block($0.timestamp)
|
|
}
|
|
}
|
|
|
|
func enumerateTimestamps(after date: Date,
|
|
count: Int,
|
|
transaction: SDSAnyReadTransaction,
|
|
block: (Date) -> Void) -> EnumerationCompletion {
|
|
// It would be more efficient to binary search here, but this is for testing.
|
|
let itemsInRange = allItems.drop { $0.timestamp < date }
|
|
return Self.enumerate(itemsInRange, count: count) {
|
|
block($0.timestamp)
|
|
}
|
|
}
|
|
|
|
func enumerateItems(in interval: DateInterval,
|
|
range: Range<Int>,
|
|
transaction: SDSAnyReadTransaction,
|
|
block: (_ offset: Int, _ uniqueId: String, _ buildItem: () -> Item) -> Void) {
|
|
// It would be more efficient to binary search here, but this is for testing.
|
|
// DateInterval is usually a *closed* range, but we're using it as a half-open one here.
|
|
let itemsInInterval = allItems.drop { $0.timestamp < interval.start }.prefix { $0.timestamp < interval.end }
|
|
let itemsInRange = itemsInInterval.dropFirst(range.startIndex).prefix(range.count)
|
|
mostRecentRequest = itemsInRange.indices
|
|
for (offset, item) in zip(range, itemsInRange) {
|
|
block(offset, item.uniqueId, { item })
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A store initialized with items in Jan, Apr, and Sep 2021.
|
|
private let standardFakeStore: FakeGalleryStore = FakeGalleryStore([
|
|
2021_01_01,
|
|
2021_01_02,
|
|
2021_01_20,
|
|
|
|
2021_04_01,
|
|
2021_04_13,
|
|
|
|
2021_09_09,
|
|
2021_09_09,
|
|
2021_09_09,
|
|
2021_09_30,
|
|
2021_09_30
|
|
].map { FakeItem($0) })
|
|
|
|
// Before we test the actual MediaGallerySections, make sure our fake store is behaving as expected.
|
|
class MediaGallerySectionsFakeStoreTest: SignalBaseTest {
|
|
func testFakeItem() {
|
|
let item1 = FakeItem(1970_01_01)
|
|
XCTAssertEqual(Calendar.current.dateComponents([.year, .month, .day], from: item1.timestamp),
|
|
DateComponents(year: 1970, month: 1, day: 1))
|
|
|
|
let item2 = FakeItem(2021_04_28)
|
|
XCTAssertEqual(Calendar.current.dateComponents([.year, .month, .day], from: item2.timestamp),
|
|
DateComponents(year: 2021, month: 4, day: 28))
|
|
XCTAssertNotEqual(item1.uniqueId, item2.uniqueId)
|
|
}
|
|
|
|
func testNumberOfItemsInSection() {
|
|
let store = standardFakeStore
|
|
|
|
databaseStorage.read { transaction in
|
|
XCTAssertEqual(3, store.numberOfItemsInSection(for: GalleryDate(2021_01_01), transaction: transaction))
|
|
XCTAssertEqual(0, store.numberOfItemsInSection(for: GalleryDate(2021_02_01), transaction: transaction))
|
|
XCTAssertEqual(2, store.numberOfItemsInSection(for: GalleryDate(2021_04_01), transaction: transaction))
|
|
XCTAssertEqual(5, store.numberOfItemsInSection(for: GalleryDate(2021_09_01), transaction: transaction))
|
|
}
|
|
}
|
|
|
|
func testEnumerateAfter() {
|
|
let store = standardFakeStore
|
|
|
|
databaseStorage.read { transaction in
|
|
var results: [Date] = []
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(after: .distantPast, count: 4, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.prefix(4).map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(after: .distantPast, count: 10, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(after: .distantPast, count: 11, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(after: Date(compressedDate: 2021_04_01),
|
|
count: 3,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems[3..<6].map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(after: Date(compressedDate: 2021_04_01),
|
|
count: 17,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems[3...].map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(after: Date(compressedDate: 2022_04_01),
|
|
count: 17,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, [])
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(after: Date(compressedDate: 2022_04_01),
|
|
count: 0,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, [])
|
|
}
|
|
}
|
|
|
|
func testEnumerateBefore() {
|
|
let store = standardFakeStore
|
|
|
|
databaseStorage.read { transaction in
|
|
var results: [Date] = []
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(before: .distantFuture, count: 4, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.reversed().prefix(4).map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(before: .distantFuture, count: 10, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.reversed().map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(before: .distantFuture, count: 11, transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems.reversed().map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.finished,
|
|
store.enumerateTimestamps(before: Date(compressedDate: 2021_04_01),
|
|
count: 2,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems[1..<3].reversed().map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(before: Date(compressedDate: 2021_04_01),
|
|
count: 17,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, store.allItems[..<3].reversed().map { $0.timestamp })
|
|
|
|
results.removeAll()
|
|
XCTAssertEqual(.reachedEnd,
|
|
store.enumerateTimestamps(before: Date(compressedDate: 2020_01_01),
|
|
count: 17,
|
|
transaction: transaction) {
|
|
results.append($0)
|
|
})
|
|
XCTAssertEqual(results, [])
|
|
}
|
|
}
|
|
|
|
func testEnumerateItems() {
|
|
let store = standardFakeStore
|
|
|
|
databaseStorage.read { transaction in
|
|
var results: [String] = []
|
|
let saveToResults = { (offset: Int, uniqueId: String, buildItem: () -> FakeItem) in
|
|
results.append(uniqueId)
|
|
XCTAssertEqual(uniqueId, buildItem().uniqueId)
|
|
}
|
|
|
|
store.enumerateItems(in: GalleryDate(2021_01_01).interval,
|
|
range: 1..<3,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual(store.allItems[1..<3].map { $0.uniqueId }, results)
|
|
|
|
results.removeAll()
|
|
store.enumerateItems(in: GalleryDate(2021_09_01).interval,
|
|
range: 2..<4,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual(store.allItems[7..<9].map { $0.uniqueId }, results)
|
|
|
|
results.removeAll()
|
|
store.enumerateItems(in: GalleryDate(2021_09_01).interval,
|
|
range: 0..<20,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual(store.allItems[5...].map { $0.uniqueId }, results)
|
|
|
|
results.removeAll()
|
|
store.enumerateItems(in: GalleryDate(2021_10_01).interval,
|
|
range: 0..<1,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual([], results)
|
|
|
|
results.removeAll()
|
|
store.enumerateItems(in: GalleryDate(2021_09_01).interval,
|
|
range: 2..<2,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual([], results)
|
|
|
|
results.removeAll()
|
|
store.enumerateItems(in: GalleryDate(2021_09_01).interval,
|
|
range: 20..<25,
|
|
transaction: transaction,
|
|
block: saveToResults)
|
|
XCTAssertEqual([], results)
|
|
}
|
|
}
|
|
}
|
|
|
|
class MediaGallerySectionsTest: SignalBaseTest {
|
|
func testLoadSectionsBackward() {
|
|
var sections = MediaGallerySections(loader: standardFakeStore)
|
|
XCTAssertEqual(sections.itemsBySection.count, 0)
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
|
|
databaseStorage.read { transaction in
|
|
XCTAssertEqual(1, sections.loadEarlierSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_09_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([5], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(2, sections.loadEarlierSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_01_01), GalleryDate(2021_04_01), GalleryDate(2021_09_01)],
|
|
sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([3, 2, 5], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(0, sections.loadEarlierSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_01_01), GalleryDate(2021_04_01), GalleryDate(2021_09_01)],
|
|
sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([3, 2, 5], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
}
|
|
}
|
|
|
|
func testLoadSectionsForward() {
|
|
var sections = MediaGallerySections(loader: standardFakeStore)
|
|
XCTAssertEqual(sections.itemsBySection.count, 0)
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
|
|
databaseStorage.read { transaction in
|
|
XCTAssertEqual(2, sections.loadLaterSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_01_01), GalleryDate(2021_04_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([3, 2], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_01_01), GalleryDate(2021_04_01), GalleryDate(2021_09_01)],
|
|
sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([3, 2, 5], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(0, sections.loadLaterSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_01_01), GalleryDate(2021_04_01), GalleryDate(2021_09_01)],
|
|
sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([3, 2, 5], sections.itemsBySection.orderedValues.map { $0.count })
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
}
|
|
}
|
|
|
|
func testStartIndexResolution() {
|
|
var sections = MediaGallerySections(loader: standardFakeStore)
|
|
databaseStorage.read { transaction in
|
|
// Load April and September
|
|
XCTAssertEqual(2, sections.loadEarlierSections(batchSize: 6, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
|
|
XCTAssertEqual(IndexPath(item: 0, section: 1),
|
|
sections.resolveNaiveStartIndex(0, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 4, section: 1),
|
|
sections.resolveNaiveStartIndex(4, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 5, section: 1),
|
|
sections.resolveNaiveStartIndex(5, relativeToSection: 1))
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0),
|
|
sections.resolveNaiveStartIndex(-1, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0),
|
|
sections.resolveNaiveStartIndex(-2, relativeToSection: 1))
|
|
XCTAssertNil(sections.resolveNaiveStartIndex(-3, relativeToSection: 1))
|
|
|
|
// Load January
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0),
|
|
sections.resolveNaiveStartIndex(-3, relativeToSection: 1) { sections in
|
|
XCTAssertEqual(1, sections.loadEarlierSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
return 1
|
|
})
|
|
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0),
|
|
sections.resolveNaiveStartIndex(-3, relativeToSection: 2))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0),
|
|
sections.resolveNaiveStartIndex(-5, relativeToSection: 2))
|
|
XCTAssertNil(sections.resolveNaiveStartIndex(-6, relativeToSection: 2))
|
|
|
|
// Find out that January was the earliest section.
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0),
|
|
sections.resolveNaiveStartIndex(-6, relativeToSection: 2) { sections in
|
|
XCTAssertEqual(0, sections.loadEarlierSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
return 0
|
|
})
|
|
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0),
|
|
sections.resolveNaiveStartIndex(-6, relativeToSection: 2))
|
|
}
|
|
}
|
|
|
|
func testEndIndexResolution() {
|
|
var sections = MediaGallerySections(loader: standardFakeStore)
|
|
databaseStorage.read { transaction in
|
|
// Load January and April
|
|
XCTAssertEqual(2, sections.loadLaterSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0),
|
|
sections.resolveNaiveEndIndex(0, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0),
|
|
sections.resolveNaiveEndIndex(2, relativeToSection: 0))
|
|
// Note: (0, 3) rather than (1, 0), because this is an end index.
|
|
XCTAssertEqual(IndexPath(item: 3, section: 0),
|
|
sections.resolveNaiveEndIndex(3, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 1),
|
|
sections.resolveNaiveEndIndex(4, relativeToSection: 0))
|
|
// Note: (1, 2) rather than nil.
|
|
XCTAssertEqual(IndexPath(item: 2, section: 1),
|
|
sections.resolveNaiveEndIndex(5, relativeToSection: 0))
|
|
XCTAssertNil(sections.resolveNaiveEndIndex(6, relativeToSection: 0))
|
|
|
|
// Load September
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
|
|
XCTAssertEqual(IndexPath(item: 2, section: 1),
|
|
sections.resolveNaiveEndIndex(5, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 2),
|
|
sections.resolveNaiveEndIndex(6, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 4, section: 2),
|
|
sections.resolveNaiveEndIndex(9, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 5, section: 2),
|
|
sections.resolveNaiveEndIndex(10, relativeToSection: 0))
|
|
// Reached end.
|
|
XCTAssertEqual(IndexPath(item: 5, section: 2),
|
|
sections.resolveNaiveEndIndex(11, relativeToSection: 0))
|
|
XCTAssertEqual(IndexPath(item: 5, section: 2),
|
|
sections.resolveNaiveEndIndex(12, relativeToSection: 0))
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 1),
|
|
sections.resolveNaiveEndIndex(1, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 1),
|
|
sections.resolveNaiveEndIndex(2, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 2),
|
|
sections.resolveNaiveEndIndex(3, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 2),
|
|
sections.resolveNaiveEndIndex(4, relativeToSection: 1))
|
|
XCTAssertEqual(IndexPath(item: 5, section: 2),
|
|
sections.resolveNaiveEndIndex(10, relativeToSection: 1))
|
|
}
|
|
}
|
|
|
|
func testLoadingFromEnd() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load April and September
|
|
XCTAssertEqual(2, sections.loadEarlierSections(batchSize: 6, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 1..<3, relativeToSection: 1).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[0].value)
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 5..<5, relativeToSection: 1).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[0].value)
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: (-2)..<3, relativeToSection: 1).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[0].value)
|
|
|
|
XCTAssertEqual(IndexSet(integer: 0), sections.ensureItemsLoaded(in: (-4)..<0, relativeToSection: 1))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[2].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testLoadingFromEndInBigJump() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load September
|
|
XCTAssertEqual(1, sections.loadEarlierSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 1..<3, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[0].value)
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 0...1), sections.ensureItemsLoaded(in: (-4)..<0, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[6], store.allItems[7], nil, nil], sections.itemsBySection[2].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testLoadingFromStart() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January and April
|
|
XCTAssertEqual(2, sections.loadLaterSections(batchSize: 4, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 1..<3, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 0..<0, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 3..<4, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], nil], sections.itemsBySection[1].value)
|
|
|
|
XCTAssertEqual(IndexSet(integer: 2), sections.ensureItemsLoaded(in: 3..<7, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], nil, nil, nil], sections.itemsBySection[2].value)
|
|
}
|
|
|
|
func testLoadingFromStartInBigJump() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 1..<3, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 0..<0, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 3..<7, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], nil, nil, nil], sections.itemsBySection[2].value)
|
|
}
|
|
|
|
func testLoadingFromMiddle() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
sections.loadInitialSection(for: GalleryDate(2021_04_01), transaction: transaction)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssert(sections.ensureItemsLoaded(in: 1..<2, relativeToSection: 0).isEmpty)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[4]], sections.itemsBySection[0].value)
|
|
|
|
XCTAssertEqual(IndexSet([0, 2]), sections.ensureItemsLoaded(in: (-2)..<5, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, store.allItems[1], store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], store.allItems[7], nil, nil],
|
|
sections.itemsBySection[2].value)
|
|
}
|
|
|
|
func testReloadSection() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 3..<7, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], nil, nil, nil], sections.itemsBySection[2].value)
|
|
|
|
databaseStorage.read { transaction in
|
|
XCTAssertEqual(2, sections.reloadSection(for: GalleryDate(2021_04_01), transaction: transaction))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], nil, nil, nil], sections.itemsBySection[2].value)
|
|
}
|
|
}
|
|
|
|
func testRemoveEmptySections() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 3..<7, relativeToSection: 0))
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
|
|
// Note: To be really accurate, we'd have to change allItems as well.
|
|
store.itemsBySection.replace(key: GalleryDate(2021_01_01), value: [])
|
|
store.itemsBySection.replace(key: GalleryDate(2021_09_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
XCTAssertEqual(0, sections.reloadSection(for: GalleryDate(2021_01_01), transaction: transaction))
|
|
XCTAssertEqual(0, sections.reloadSection(for: GalleryDate(2021_09_01), transaction: transaction))
|
|
XCTAssertEqual([], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([], sections.itemsBySection[2].value)
|
|
|
|
sections.removeEmptySections(atIndexes: IndexSet([0, 2]))
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_04_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[0].value)
|
|
}
|
|
}
|
|
|
|
func testResetWhenEmpty() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssert(sections.itemsBySection.isEmpty)
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
}
|
|
|
|
func testResetOneSection() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testResetTwoSections() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integer: 1), sections.ensureItemsLoaded(in: 3..<4, relativeToSection: 0))
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], nil], sections.itemsBySection[1].value)
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
}
|
|
|
|
func testResetFull() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 2..<7, relativeToSection: 0))
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, store.allItems[2]], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.allItems[3], store.allItems[4]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[6], nil, nil, nil], sections.itemsBySection[2].value)
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[2].value)
|
|
}
|
|
|
|
func testResetOneSectionAfterDeletingStart() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
store.allItems.removeFirst(3)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_01_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_09_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testResetTwoSectionsAfterDeletingStart() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integer: 1), sections.ensureItemsLoaded(in: 3..<4, relativeToSection: 0))
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
|
|
store.allItems.removeFirst(3)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_01_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_09_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testResetTwoSectionsAfterDeletingEndWithAnotherSectionFollowing() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integer: 1), sections.ensureItemsLoaded(in: 3..<4, relativeToSection: 0))
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
|
|
store.allItems.removeSubrange(3..<5)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_04_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[1].value)
|
|
}
|
|
|
|
func testResetTwoSectionsAfterDeletingEndWithNothingFollowing() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load April and September
|
|
XCTAssertEqual(2, sections.loadEarlierSections(batchSize: 6, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
}
|
|
|
|
store.allItems.removeSubrange(5...)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_09_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([GalleryDate(2021_04_01)], sections.itemsBySection.orderedKeys)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testResetFullAfterDeletingStart() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 2..<7, relativeToSection: 0))
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
|
|
store.allItems.removeFirst(3)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_01_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[1].value)
|
|
}
|
|
|
|
func testResetFullAfterDeletingMiddle() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 2..<7, relativeToSection: 0))
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
|
|
store.allItems.removeSubrange(3..<5)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_04_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil, nil, nil, nil], sections.itemsBySection[1].value)
|
|
}
|
|
|
|
func testResetFullAfterDeletingEnd() {
|
|
let store = standardFakeStore.clone()
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexSet(integersIn: 1...2), sections.ensureItemsLoaded(in: 2..<7, relativeToSection: 0))
|
|
XCTAssertTrue(sections.hasFetchedOldest)
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
|
|
store.allItems.removeSubrange(5...)
|
|
store.itemsBySection.replace(key: GalleryDate(2021_09_01), value: [])
|
|
|
|
databaseStorage.read { transaction in
|
|
sections.reset(transaction: transaction)
|
|
}
|
|
|
|
XCTAssertFalse(sections.hasFetchedOldest)
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(2, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
XCTAssertEqual([nil, nil], sections.itemsBySection[1].value)
|
|
}
|
|
|
|
func testGetOrAddItem() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
let fakeItem = FakeItem(2021_01_05)
|
|
let newItem = sections.getOrReplaceItem(fakeItem, offsetInSection: 1)
|
|
XCTAssertEqual(fakeItem, newItem)
|
|
XCTAssertEqual([nil, fakeItem, nil], sections.itemsBySection[0].value)
|
|
|
|
let fakeItem2 = FakeItem(2021_01_06)
|
|
let newItem2 = sections.getOrReplaceItem(fakeItem2, offsetInSection: 1)
|
|
XCTAssertEqual(fakeItem, newItem2)
|
|
XCTAssertEqual([nil, fakeItem, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testIndexAfter() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0), sections.indexPath(after: IndexPath(item: 0, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0), sections.indexPath(after: IndexPath(item: 1, section: 0)))
|
|
XCTAssertNil(sections.indexPath(after: IndexPath(item: 2, section: 0)))
|
|
|
|
databaseStorage.read { transaction in
|
|
// Load remaining sections
|
|
XCTAssertEqual(2, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0), sections.indexPath(after: IndexPath(item: 0, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0), sections.indexPath(after: IndexPath(item: 1, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 1), sections.indexPath(after: IndexPath(item: 2, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 1), sections.indexPath(after: IndexPath(item: 0, section: 1)))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 2), sections.indexPath(after: IndexPath(item: 1, section: 1)))
|
|
XCTAssertNil(sections.indexPath(after: IndexPath(item: 4, section: 2)))
|
|
}
|
|
|
|
func testIndexBefore() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load January
|
|
XCTAssertEqual(1, sections.loadLaterSections(batchSize: 1, transaction: transaction))
|
|
XCTAssertFalse(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(1, sections.itemsBySection.count)
|
|
XCTAssertEqual([nil, nil, nil], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0), sections.indexPath(before: IndexPath(item: 2, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0), sections.indexPath(before: IndexPath(item: 1, section: 0)))
|
|
XCTAssertNil(sections.indexPath(before: IndexPath(item: 0, section: 0)))
|
|
|
|
databaseStorage.read { transaction in
|
|
// Load remaining sections
|
|
XCTAssertEqual(2, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
XCTAssertEqual(IndexPath(item: 1, section: 1), sections.indexPath(before: IndexPath(item: 0, section: 2)))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 1), sections.indexPath(before: IndexPath(item: 1, section: 1)))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0), sections.indexPath(before: IndexPath(item: 0, section: 1)))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0), sections.indexPath(before: IndexPath(item: 2, section: 0)))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0), sections.indexPath(before: IndexPath(item: 1, section: 0)))
|
|
XCTAssertNil(sections.indexPath(before: IndexPath(item: 0, section: 0)))
|
|
}
|
|
|
|
func testIndexPathOf() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load all months
|
|
XCTAssertEqual(3, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
// Load all items.
|
|
XCTAssert(sections.ensureItemsLoaded(in: 0..<20, relativeToSection: 0).isEmpty)
|
|
|
|
XCTAssertEqual(IndexPath(item: 0, section: 0), sections.indexPath(for: store.allItems[0]))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 0), sections.indexPath(for: store.allItems[1]))
|
|
XCTAssertEqual(IndexPath(item: 2, section: 0), sections.indexPath(for: store.allItems[2]))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 1), sections.indexPath(for: store.allItems[3]))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 1), sections.indexPath(for: store.allItems[4]))
|
|
XCTAssertEqual(IndexPath(item: 0, section: 2), sections.indexPath(for: store.allItems[5]))
|
|
XCTAssertEqual(IndexPath(item: 1, section: 2), sections.indexPath(for: store.allItems[6]))
|
|
|
|
// Different uniqueId -> no match, even though the timestamp matches.
|
|
XCTAssert(store.allItems.contains { $0.timestamp == Date(compressedDate: 2021_09_09) })
|
|
XCTAssertNil(sections.indexPath(for: FakeItem(2021_09_09)))
|
|
}
|
|
|
|
func testRemoveLoadedItems() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load all months
|
|
XCTAssertEqual(3, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
// Load all items.
|
|
XCTAssert(sections.ensureItemsLoaded(in: 0..<20, relativeToSection: 0).isEmpty)
|
|
|
|
XCTAssert(sections.removeLoadedItems(atIndexPaths: [IndexPath(item: 1, section: 1)]).isEmpty)
|
|
XCTAssertEqual(store.itemsBySection[0].value.map { $0 }, sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.itemsBySection[1].value[0]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual(store.itemsBySection[2].value.map { $0 }, sections.itemsBySection[2].value)
|
|
|
|
XCTAssert(sections.removeLoadedItems(atIndexPaths: [IndexPath(item: 1, section: 2),
|
|
IndexPath(item: 3, section: 2)]).isEmpty)
|
|
XCTAssertEqual(store.itemsBySection[0].value.map { $0 }, sections.itemsBySection[0].value)
|
|
XCTAssertEqual([store.itemsBySection[1].value[0]], sections.itemsBySection[1].value)
|
|
XCTAssertEqual([store.allItems[5], store.allItems[7], store.allItems[9]], sections.itemsBySection[2].value)
|
|
|
|
XCTAssertEqual(IndexSet([0, 2]),
|
|
sections.removeLoadedItems(atIndexPaths: [IndexPath(item: 0, section: 0),
|
|
IndexPath(item: 1, section: 0),
|
|
IndexPath(item: 2, section: 0),
|
|
IndexPath(item: 0, section: 2),
|
|
IndexPath(item: 1, section: 2),
|
|
IndexPath(item: 2, section: 2)]))
|
|
XCTAssertEqual([store.itemsBySection[1].value[0]], sections.itemsBySection[0].value)
|
|
}
|
|
|
|
func testTrimFromStart() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load all months
|
|
XCTAssertEqual(3, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
// _ _ _ | _ _ | x _ x x _
|
|
_ = sections.ensureItemsLoaded(in: 0..<1, relativeToSection: 2)
|
|
_ = sections.ensureItemsLoaded(in: 2..<4, relativeToSection: 2)
|
|
|
|
XCTAssertEqual(1..<4, sections.trimLoadedItemsAtStart(from: 0..<4, relativeToSection: 2))
|
|
XCTAssertEqual(1..<5, sections.trimLoadedItemsAtStart(from: 0..<5, relativeToSection: 2))
|
|
// End index is not even checked.
|
|
XCTAssertEqual(1..<6, sections.trimLoadedItemsAtStart(from: 0..<6, relativeToSection: 2))
|
|
|
|
XCTAssertEqual(1..<4, sections.trimLoadedItemsAtStart(from: 1..<4, relativeToSection: 2))
|
|
XCTAssertEqual(4..<4, sections.trimLoadedItemsAtStart(from: 2..<4, relativeToSection: 2))
|
|
XCTAssertEqual(4..<5, sections.trimLoadedItemsAtStart(from: 2..<5, relativeToSection: 2))
|
|
|
|
XCTAssertEqual(-1 ..< 5, sections.trimLoadedItemsAtStart(from: -1 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-5 ..< 5, sections.trimLoadedItemsAtStart(from: -5 ..< 5, relativeToSection: 2))
|
|
|
|
// _ _ _ | _ x | x _ x x _
|
|
_ = sections.ensureItemsLoaded(in: 1..<2, relativeToSection: 1)
|
|
|
|
XCTAssertEqual(1..<5, sections.trimLoadedItemsAtStart(from: -1 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-2 ..< 5, sections.trimLoadedItemsAtStart(from: -2 ..< 5, relativeToSection: 2))
|
|
// trimLoadedItemsAtStart never goes past the current section.
|
|
XCTAssertEqual(2..<4, sections.trimLoadedItemsAtStart(from: 1..<4, relativeToSection: 1))
|
|
XCTAssertEqual(0..<4, sections.trimLoadedItemsAtStart(from: 0..<4, relativeToSection: 1))
|
|
|
|
// _ _ _ | x x | x _ x x _
|
|
_ = sections.ensureItemsLoaded(in: 0..<2, relativeToSection: 1)
|
|
|
|
XCTAssertEqual(1..<5, sections.trimLoadedItemsAtStart(from: -1 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(1..<5, sections.trimLoadedItemsAtStart(from: -2 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-3 ..< 5, sections.trimLoadedItemsAtStart(from: -3 ..< 5, relativeToSection: 2))
|
|
// trimLoadedItemsAtStart never goes past the current section.
|
|
XCTAssertEqual(2..<4, sections.trimLoadedItemsAtStart(from: 1..<4, relativeToSection: 1))
|
|
XCTAssertEqual(2..<4, sections.trimLoadedItemsAtStart(from: 0..<4, relativeToSection: 1))
|
|
|
|
// _ _ x | x _ | x _ x x _
|
|
databaseStorage.read { transaction in
|
|
sections.reloadSection(for: sections.sectionDates[1], transaction: transaction)
|
|
}
|
|
_ = sections.ensureItemsLoaded(in: -1 ..< 1, relativeToSection: 1)
|
|
XCTAssertEqual(-1 ..< 5, sections.trimLoadedItemsAtStart(from: -1 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-1 ..< 5, sections.trimLoadedItemsAtStart(from: -2 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-1 ..< 5, sections.trimLoadedItemsAtStart(from: -3 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(-4 ..< 5, sections.trimLoadedItemsAtStart(from: -4 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(1..<4, sections.trimLoadedItemsAtStart(from: 1..<4, relativeToSection: 1))
|
|
XCTAssertEqual(1..<4, sections.trimLoadedItemsAtStart(from: 0..<4, relativeToSection: 1))
|
|
|
|
// x x x | x x | x x x x x
|
|
_ = sections.ensureItemsLoaded(in: 0..<10, relativeToSection: 0)
|
|
XCTAssertEqual(5..<5, sections.trimLoadedItemsAtStart(from: -5 ..< 5, relativeToSection: 2))
|
|
XCTAssertEqual(3..<3, sections.trimLoadedItemsAtStart(from: -5 ..< 3, relativeToSection: 2))
|
|
// trimLoadedItemsAtStart never goes past the current section.
|
|
XCTAssertEqual(2..<7, sections.trimLoadedItemsAtStart(from: -3 ..< 7, relativeToSection: 1))
|
|
}
|
|
|
|
func testTrimFromEnd() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load all months
|
|
XCTAssertEqual(3, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
// x x _ | _ _ | _ _ _ _ _
|
|
_ = sections.ensureItemsLoaded(in: 0..<2, relativeToSection: 0)
|
|
|
|
XCTAssertEqual(0..<0, sections.trimLoadedItemsAtEnd(from: 0..<1, relativeToSection: 0))
|
|
XCTAssertEqual(0..<0, sections.trimLoadedItemsAtEnd(from: 0..<2, relativeToSection: 0))
|
|
XCTAssertEqual(0..<3, sections.trimLoadedItemsAtEnd(from: 0..<3, relativeToSection: 0))
|
|
XCTAssertEqual(0..<4, sections.trimLoadedItemsAtEnd(from: 0..<4, relativeToSection: 0))
|
|
// We don't actually check the start index.
|
|
XCTAssertEqual(-5 ..< 0, sections.trimLoadedItemsAtEnd(from: -5 ..< 2, relativeToSection: 0))
|
|
}
|
|
|
|
func testLoadingTrimsRequestedRange() {
|
|
let store = standardFakeStore
|
|
var sections = MediaGallerySections(loader: store)
|
|
databaseStorage.read { transaction in
|
|
// Load all months
|
|
XCTAssertEqual(3, sections.loadLaterSections(batchSize: 20, transaction: transaction))
|
|
XCTAssertTrue(sections.hasFetchedMostRecent)
|
|
XCTAssertEqual(3, sections.itemsBySection.count)
|
|
}
|
|
|
|
// _ _ x | x x | x _ _ _ _
|
|
_ = sections.ensureItemsLoaded(in: -1 ..< 3, relativeToSection: 1)
|
|
XCTAssertEqual(2..<6, store.mostRecentRequest)
|
|
|
|
// _ _ x | x x | x x x _ _
|
|
_ = sections.ensureItemsLoaded(in: 0 ..< 5, relativeToSection: 1)
|
|
// We only trim up to the current section, so the first item in September gets reloaded.
|
|
XCTAssertEqual(5..<8, store.mostRecentRequest)
|
|
|
|
// x x x | x x | x x x _ _
|
|
_ = sections.ensureItemsLoaded(in: -3 ..< 1, relativeToSection: 1)
|
|
// We only trim down to the current section, so the last item in January gets reloaded.
|
|
XCTAssertEqual(0..<3, store.mostRecentRequest)
|
|
|
|
// x x x | _ _ | x x x _ _
|
|
databaseStorage.read { transaction in
|
|
sections.reloadSection(for: sections.sectionDates[1], transaction: transaction)
|
|
}
|
|
|
|
// x x x | x x | x x x _ _
|
|
_ = sections.ensureItemsLoaded(in: -1 ..< 3, relativeToSection: 1)
|
|
XCTAssertEqual(3..<5, store.mostRecentRequest)
|
|
|
|
store.mostRecentRequest = 5000..<5000
|
|
_ = sections.ensureItemsLoaded(in: -1 ..< 3, relativeToSection: 1)
|
|
// The request should be skipped entirely.
|
|
XCTAssertEqual(5000..<5000, store.mostRecentRequest)
|
|
}
|
|
}
|