mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-24 12:51:22 +08:00
fix(import): 修复本地 TXT 文件选择与导入
- 书架和文件管理统一使用 TXT 文件选择器,支持大小写扩展名及多选,排除 JSON 书源 - 保留所选文件访问权限至异步导入完成,等待选择器关闭后展示导入确认 - 补充双入口文件选择回归、导入校验和 TXT 测试样本
This commit is contained in:
@@ -5,6 +5,38 @@ import LegadoCore
|
||||
|
||||
@MainActor
|
||||
struct LocalFileManagementTests {
|
||||
@Test func pickedTXTImportsButJSONFilesCannotBecomeLocalBooks() async throws {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: root) }
|
||||
let inputs = [
|
||||
("小说.TXT", "第一章 开始\n本地小说的正文"),
|
||||
("书源.json", #"{"bookSourceName":"书源"}"#),
|
||||
("大写书源.JSON", #"{"bookSourceName":"书源"}"#)
|
||||
]
|
||||
let files = try inputs.map { name, text in
|
||||
let url = root.appendingPathComponent(name)
|
||||
try Data(text.utf8).write(to: url)
|
||||
return LocalBookImportFile(url: url)
|
||||
}
|
||||
let model = BookLibraryModel(repository: InMemoryBookRepository(),
|
||||
localDirectory: root.appendingPathComponent("managed"),
|
||||
chapterRepository: InMemoryBookChapterRepository())
|
||||
await model.load()
|
||||
|
||||
await model.importPickedLocalFiles(files)
|
||||
|
||||
let book = try #require(model.books.first)
|
||||
#expect(model.books.count == 1)
|
||||
#expect(book.origin == "local")
|
||||
#expect(book.originName == "小说.TXT")
|
||||
#expect(model.errorMessage?.contains("书源.json") == true)
|
||||
#expect(model.errorMessage?.contains("大写书源.JSON") == true)
|
||||
#expect(try await model.localFiles.list().count == 1)
|
||||
let imported = try #require(URL(string: book.bookUrl))
|
||||
#expect(try Data(contentsOf: imported) == Data(inputs[0].1.utf8))
|
||||
}
|
||||
|
||||
private func fixture(fails: Bool = false) async throws -> (URL, Book, BookLibraryModel) {
|
||||
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
||||
|
||||
@@ -2,6 +2,92 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
final class RuleEditorUITests: XCTestCase {
|
||||
func testBookshelfTXTFilePickerImportsTXTAndDisablesJSON() throws {
|
||||
try exerciseLocalTXTImport(fromFileManagement: false)
|
||||
}
|
||||
|
||||
func testFileManagementTXTFilePickerImportsTXTAndDisablesJSON() throws {
|
||||
try exerciseLocalTXTImport(fromFileManagement: true)
|
||||
}
|
||||
|
||||
private func exerciseLocalTXTImport(fromFileManagement: Bool) throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-themeMode", "1", "-bookshelfLayout", "grid3"]
|
||||
if fromFileManagement { app.launchArguments += ["-uiDemoTab", "settings", "-uiDemoFiles"] }
|
||||
app.launch()
|
||||
let importTitle = fromFileManagement ? "导入 TXT 文件" : "本地书"
|
||||
let more = fromFileManagement
|
||||
? app.navigationBars["文件管理"].buttons["更多"].firstMatch
|
||||
: app.buttons["更多"].firstMatch
|
||||
XCTAssertTrue(more.waitForExistence(timeout: 20))
|
||||
more.tap()
|
||||
app.buttons[importTitle].tap()
|
||||
let cancel = app.buttons.matching(NSPredicate(format: "label IN %@", ["Cancel", "取消"])).firstMatch
|
||||
XCTAssertTrue(cancel.waitForExistence(timeout: 10))
|
||||
cancel.tap()
|
||||
let closed = expectation(for: NSPredicate { _, _ in !cancel.exists }, evaluatedWith: nil)
|
||||
wait(for: [closed], timeout: 10)
|
||||
more.tap()
|
||||
app.buttons[importTitle].tap()
|
||||
XCTAssertTrue(cancel.waitForExistence(timeout: 10))
|
||||
|
||||
let pickerFiles = app.collectionViews["File View"].cells
|
||||
let txt = pickerFiles.matching(NSPredicate(format: "label BEGINSWITH %@", "TXT-Book")).firstMatch
|
||||
let fixtureJSON = pickerFiles.matching(NSPredicate(format: "label BEGINSWITH %@", "JSON-Source")).firstMatch
|
||||
let fixtures = pickerFiles.matching(NSPredicate(format: "label BEGINSWITH %@", "书源导入测试")).firstMatch
|
||||
// The file-management page underneath the picker can also contain TXT.
|
||||
// JSON exists only inside our fixture folder in the actual picker.
|
||||
if !fixtureJSON.waitForExistence(timeout: 3) {
|
||||
if !fixtures.exists {
|
||||
let browse = app.buttons.matching(NSPredicate(format: "label IN %@", ["Browse", "浏览", "Locations", "位置"])).firstMatch
|
||||
if browse.exists { browse.tap() }
|
||||
if !fixtures.waitForExistence(timeout: 2) {
|
||||
let local = app.descendants(matching: .any).matching(NSPredicate(
|
||||
format: "label CONTAINS %@ OR label CONTAINS %@ OR label == %@", "我的 iPhone", "我的iPhone", "On My iPhone"
|
||||
)).firstMatch
|
||||
XCTAssertTrue(local.waitForExistence(timeout: 5))
|
||||
local.tap()
|
||||
}
|
||||
}
|
||||
XCTAssertTrue(fixtures.waitForExistence(timeout: 10), "Run sh scripts/prepare-source-import-fixture.sh <simulator-UUID>")
|
||||
fixtures.tap()
|
||||
}
|
||||
XCTAssertTrue(txt.waitForExistence(timeout: 10))
|
||||
let open = app.buttons.matching(NSPredicate(format: "label IN %@", ["Open", "打开", "Import", "导入"])).firstMatch
|
||||
for name in ["JSON-Source", "JSON-Uppercase"] {
|
||||
let json = pickerFiles.matching(NSPredicate(format: "label BEGINSWITH %@", name)).firstMatch
|
||||
XCTAssertTrue(json.waitForExistence(timeout: 5))
|
||||
XCTAssertFalse(json.isEnabled)
|
||||
json.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
XCTAssertFalse(open.exists && open.isEnabled, "JSON must not be selectable as a local book")
|
||||
}
|
||||
txt.tap()
|
||||
let uppercase = pickerFiles.matching(NSPredicate(format: "label BEGINSWITH %@", "TXT-Uppercase")).firstMatch
|
||||
XCTAssertTrue(uppercase.waitForExistence(timeout: 5))
|
||||
uppercase.tap()
|
||||
XCTAssertTrue(open.waitForExistence(timeout: 5))
|
||||
XCTAssertTrue(open.isEnabled)
|
||||
attach(app, name: "TXT selectable and JSON excluded")
|
||||
open.tap()
|
||||
|
||||
if fromFileManagement {
|
||||
XCTAssertTrue(app.navigationBars["文件管理"].waitForExistence(timeout: 15))
|
||||
app.tabBars.buttons["书架"].tap()
|
||||
} else {
|
||||
XCTAssertTrue(app.navigationBars["导入本地书"].waitForExistence(timeout: 15))
|
||||
XCTAssertFalse(app.staticTexts["JSON-Source.json"].exists)
|
||||
XCTAssertFalse(app.staticTexts["JSON-Uppercase.JSON"].exists)
|
||||
XCTAssertTrue(app.staticTexts["TXT-Book.txt"].exists)
|
||||
XCTAssertTrue(app.staticTexts["TXT-Uppercase.TXT"].exists)
|
||||
app.buttons["导入"].tap()
|
||||
}
|
||||
// Repeated runs preserve managed files and correctly add a numeric suffix.
|
||||
XCTAssertTrue(app.staticTexts.matching(NSPredicate(format: "label BEGINSWITH %@", "TXT-Book")).firstMatch.waitForExistence(timeout: 15))
|
||||
XCTAssertTrue(app.staticTexts.matching(NSPredicate(format: "label BEGINSWITH %@", "TXT-Uppercase")).firstMatch.waitForExistence(timeout: 15))
|
||||
attach(app, name: "Local TXT books imported onto shelf")
|
||||
}
|
||||
|
||||
func testJSONFilePickerOffersOpenAndImportsSelectedFiles() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
|
||||
Reference in New Issue
Block a user