test: 增强 UI 测试基础设施与阅读器自动化测试
- 新增 BookmarkTests/PageNavigationTests/TableOfContentsTests 等阅读器功能测试 - 扩展 AccessibilityIdentifiers 支持更多 UI 元素定位 - XCUIApplication+Launch: 增加启动参数支持(reset state、自定义书籍) - ReaderAnnotationTests: 完善标注测试覆盖 - ViewController: 支持 --demo-reset-state 参数清理持久化状态 - 新增凡人修仙传测试样本 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
c76beed03c
commit
9801af05d3
@@ -12,15 +12,30 @@ enum IDs {
|
||||
static let readerBottomToolbar = "epub.reader.bottomToolbar"
|
||||
static let readerContent = "epub.reader.content"
|
||||
static let readerPaging = "epub.reader.paging"
|
||||
static let readerBookmark = "epub.reader.bookmark"
|
||||
static let readerToc = "epub.reader.toc"
|
||||
static let readerBookmarks = "epub.reader.bookmarks"
|
||||
static let readerHighlights = "epub.reader.highlights"
|
||||
static let readerAddHighlight = "epub.reader.add-highlight"
|
||||
static let readerSettings = "epub.reader.settings"
|
||||
static let readerTocPanel = "epub.reader.toc.panel"
|
||||
static let readerTocTable = "epub.reader.toc.table"
|
||||
static let readerTocNavBar = "epub.reader.toc.navbar"
|
||||
static let readerBookmarksPanel = "epub.reader.bookmarks.panel"
|
||||
static let readerBookmarksTable = "epub.reader.bookmarks.table"
|
||||
static let readerBookmarksNavBar = "epub.reader.bookmarks.navbar"
|
||||
static let readerSelectionText = "epub.reader.selection.text"
|
||||
static let readerSelectionHighlight = "epub.reader.selection.高亮"
|
||||
|
||||
static let settingsScroll = "epub.reader.settings.scroll"
|
||||
static let settingsBrightness = "epub.reader.settings.brightness"
|
||||
static let settingsFontIncrease = "epub.reader.settings.font.increase"
|
||||
static let settingsFontDecrease = "epub.reader.settings.font.decrease"
|
||||
static let settingsFontValue = "epub.reader.settings.font.value"
|
||||
static let settingsFontChoice = "epub.reader.settings.font.choice"
|
||||
static let settingsLineHeight = "epub.reader.settings.lineHeight"
|
||||
static let settingsColumns = "epub.reader.settings.columns"
|
||||
static let settingsDisplayType = "epub.reader.settings.displayType"
|
||||
static let settingsDone = "epub.reader.settings.done"
|
||||
static func settingsTheme(_ index: Int) -> String { "epub.reader.settings.theme.\(index)" }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import XCTest
|
||||
|
||||
extension XCUIApplication {
|
||||
func launchAndOpenSampleBook(displayType: String? = nil, pageNumber: Int? = nil) {
|
||||
var args = ["--demo-book-title", "回归验证样本"]
|
||||
func launchAndOpenSampleBook(
|
||||
bookTitleQuery: String = "回归验证样本",
|
||||
displayType: String? = nil,
|
||||
pageNumber: Int? = nil,
|
||||
resetsReaderState: Bool = true
|
||||
) {
|
||||
var args = ["--demo-book-title", bookTitleQuery]
|
||||
if resetsReaderState {
|
||||
args.append("--demo-reset-state")
|
||||
}
|
||||
if let displayType {
|
||||
args += ["--demo-display-type", displayType]
|
||||
}
|
||||
@@ -21,6 +29,10 @@ extension XCUIApplication {
|
||||
return state
|
||||
}
|
||||
|
||||
func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) {
|
||||
waitForReaderState(containing: "page=\(pageNumber)", timeout: timeout)
|
||||
}
|
||||
|
||||
func showReaderChromeIfNeeded() {
|
||||
if buttons[IDs.readerBack].exists && buttons[IDs.readerSettings].exists {
|
||||
return
|
||||
@@ -34,18 +46,33 @@ extension XCUIApplication {
|
||||
}
|
||||
}
|
||||
|
||||
func hideReaderChromeIfNeeded() {
|
||||
for _ in 0..<3 {
|
||||
guard buttons[IDs.readerBack].exists else { return }
|
||||
|
||||
let content = otherElements[IDs.readerContent]
|
||||
if content.waitForExistence(timeout: 2) {
|
||||
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.6)).tap()
|
||||
}
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
|
||||
}
|
||||
}
|
||||
|
||||
func waitForReaderState(containing expectedText: String, timeout: TimeInterval = 8) {
|
||||
let state = staticTexts[IDs.demoReaderState]
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
var lastLabel = "<missing>"
|
||||
|
||||
while Date() < deadline {
|
||||
if state.exists && state.label.contains(expectedText) {
|
||||
return
|
||||
if state.exists {
|
||||
lastLabel = state.label
|
||||
if lastLabel.contains(expectedText) {
|
||||
return
|
||||
}
|
||||
}
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
||||
}
|
||||
|
||||
let currentLabel = state.exists ? state.label : "<missing>"
|
||||
XCTFail("阅读器状态未包含 \(expectedText),当前:\(currentLabel)")
|
||||
XCTFail("阅读器状态未包含 \(expectedText),当前:\(lastLabel)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import XCTest
|
||||
|
||||
final class BookmarkTests: XCTestCase {
|
||||
private let app = XCUIApplication()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testBookmarkButtonExistsInToolbar() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let bookmarkButton = app.buttons[IDs.readerBookmark]
|
||||
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
|
||||
}
|
||||
|
||||
func testToggleBookmark() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let bookmarkButton = app.buttons[IDs.readerBookmark]
|
||||
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
|
||||
bookmarkButton.tap()
|
||||
Thread.sleep(forTimeInterval: 1)
|
||||
|
||||
app.showReaderChromeIfNeeded()
|
||||
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮再次点击后未出现")
|
||||
bookmarkButton.tap()
|
||||
}
|
||||
|
||||
func testBookmarksButtonVisibleAfterBookmarkAdded() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let bookmarkButton = app.buttons[IDs.readerBookmark]
|
||||
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
|
||||
bookmarkButton.tap()
|
||||
Thread.sleep(forTimeInterval: 1)
|
||||
|
||||
app.showReaderChromeIfNeeded()
|
||||
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
|
||||
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
|
||||
}
|
||||
|
||||
func testBookmarksListButtonExists() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
|
||||
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
|
||||
}
|
||||
|
||||
func testBookmarksPanelIsAccessible() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let bookmarkButton = app.buttons[IDs.readerBookmark]
|
||||
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
|
||||
bookmarkButton.tap()
|
||||
|
||||
app.showReaderChromeIfNeeded()
|
||||
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
|
||||
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
|
||||
|
||||
let enabledPredicate = NSPredicate(format: "isEnabled == true")
|
||||
expectation(for: enabledPredicate, evaluatedWith: bookmarksListButton)
|
||||
waitForExpectations(timeout: 10)
|
||||
|
||||
XCTAssertTrue(bookmarksListButton.isEnabled, "书签列表按钮未启用")
|
||||
bookmarksListButton.tap()
|
||||
|
||||
let bookmarksPanel = app.tables[IDs.readerBookmarksTable]
|
||||
XCTAssertTrue(bookmarksPanel.waitForExistence(timeout: 5), "书签面板未出现")
|
||||
XCTAssertTrue(app.navigationBars[IDs.readerBookmarksNavBar].waitForExistence(timeout: 2), "书签导航栏未暴露")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import XCTest
|
||||
|
||||
final class PageNavigationTests: XCTestCase {
|
||||
private let app = XCUIApplication()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testContentAreaExistsForNavigation() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
|
||||
let content = app.otherElements[IDs.readerContent]
|
||||
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
|
||||
}
|
||||
|
||||
func testPagingViewExistsInScrollMode() {
|
||||
app.launchAndOpenSampleBook(displayType: "scroll")
|
||||
app.waitForReader()
|
||||
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
|
||||
|
||||
let paging = app.collectionViews[IDs.readerPaging]
|
||||
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页滚动视图未出现")
|
||||
}
|
||||
|
||||
func testPagingViewExistsInVerticalScrollMode() {
|
||||
app.launchAndOpenSampleBook(displayType: "vertical")
|
||||
app.waitForReader()
|
||||
app.waitForReaderState(containing: "display=verticalScroll", timeout: 5)
|
||||
|
||||
let paging = app.collectionViews[IDs.readerPaging]
|
||||
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页滚动视图未出现")
|
||||
}
|
||||
|
||||
func testSwipeInHorizontalScrollMode() {
|
||||
app.launchAndOpenSampleBook(displayType: "scroll")
|
||||
app.waitForReader()
|
||||
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
|
||||
|
||||
let paging = app.collectionViews[IDs.readerPaging]
|
||||
XCTAssertTrue(paging.waitForExistence(timeout: 5))
|
||||
|
||||
let state = app.staticTexts[IDs.demoReaderState]
|
||||
let beforeLabel = state.label
|
||||
|
||||
paging.swipeLeft()
|
||||
let deadline = Date().addingTimeInterval(5)
|
||||
while Date() < deadline {
|
||||
if state.exists && state.label != beforeLabel { break }
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
||||
}
|
||||
XCTAssertNotEqual(beforeLabel, state.label, "水平滑动后页码未变化")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import XCTest
|
||||
|
||||
final class ReaderAnnotationExtendedTests: XCTestCase {
|
||||
private let app = XCUIApplication()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testBottomToolbarHasAddHighlightButton() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let addHighlightButton = app.buttons[IDs.readerAddHighlight]
|
||||
XCTAssertTrue(addHighlightButton.waitForExistence(timeout: 5), "底部工具栏标注按钮未出现")
|
||||
}
|
||||
|
||||
func testBottomToolbarHasHighlightsButton() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let highlightsButton = app.buttons[IDs.readerHighlights]
|
||||
XCTAssertTrue(highlightsButton.waitForExistence(timeout: 5), "底部工具栏高亮列表按钮未出现")
|
||||
}
|
||||
|
||||
func testBottomToolbarButtonsLayout() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
XCTAssertTrue(app.buttons[IDs.readerToc].waitForExistence(timeout: 5), "目录按钮未出现")
|
||||
XCTAssertTrue(app.buttons[IDs.readerBookmarks].exists, "书签按钮未出现")
|
||||
XCTAssertTrue(app.buttons[IDs.readerHighlights].exists, "高亮按钮未出现")
|
||||
XCTAssertTrue(app.buttons[IDs.readerAddHighlight].exists, "标注按钮未出现")
|
||||
XCTAssertTrue(app.buttons[IDs.readerSettings].exists, "设置按钮未出现")
|
||||
}
|
||||
|
||||
func testSelectionTextElementExists() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
|
||||
let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch
|
||||
XCTAssertTrue(selectionText.waitForExistence(timeout: 5), "文本选区元素未出现")
|
||||
}
|
||||
|
||||
func testReaderStateReportsPageInfo() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
|
||||
let state = app.staticTexts[IDs.demoReaderState]
|
||||
XCTAssertTrue(state.waitForExistence(timeout: 5))
|
||||
XCTAssertTrue(state.label.contains("reader=opened"), "状态未包含 reader=opened")
|
||||
XCTAssertTrue(state.label.contains("page="), "状态未包含 page 信息")
|
||||
XCTAssertTrue(state.label.contains("display="), "状态未包含 display 信息")
|
||||
}
|
||||
|
||||
func testReaderStateReportsHighlightCount() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
|
||||
let state = app.staticTexts[IDs.demoReaderState]
|
||||
XCTAssertTrue(state.waitForExistence(timeout: 5))
|
||||
XCTAssertTrue(state.label.contains("highlights=0"), "初始高亮数应为 0")
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,17 @@ final class ReaderAnnotationTests: XCTestCase {
|
||||
func testSelectionMenuCreatesHighlight() {
|
||||
app.launchAndOpenSampleBook(pageNumber: 2)
|
||||
app.waitForReader()
|
||||
app.waitForReaderPage(2)
|
||||
app.hideReaderChromeIfNeeded()
|
||||
|
||||
let content = app.otherElements[IDs.readerSelectionText].firstMatch
|
||||
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
|
||||
|
||||
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.42))
|
||||
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.42))
|
||||
start.press(forDuration: 0.6, thenDragTo: end)
|
||||
app.waitForReaderState(containing: "toolbar=hidden", timeout: 5)
|
||||
|
||||
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.45))
|
||||
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.45))
|
||||
start.press(forDuration: 0.8, thenDragTo: end)
|
||||
|
||||
app.waitForReaderState(containing: "selection=1", timeout: 5)
|
||||
|
||||
@@ -36,15 +40,19 @@ final class ReaderAnnotationTests: XCTestCase {
|
||||
func testLongPressSelectionDoesNotShowToolbars() {
|
||||
app.launchAndOpenSampleBook(pageNumber: 2)
|
||||
app.waitForReader()
|
||||
app.waitForReaderPage(2)
|
||||
app.hideReaderChromeIfNeeded()
|
||||
|
||||
let content = app.otherElements[IDs.readerSelectionText].firstMatch
|
||||
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
|
||||
|
||||
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.42))
|
||||
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.42))
|
||||
start.press(forDuration: 0.6, thenDragTo: end)
|
||||
|
||||
app.waitForReaderState(containing: "selection=1", timeout: 5)
|
||||
app.waitForReaderState(containing: "toolbar=hidden", timeout: 5)
|
||||
|
||||
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.45))
|
||||
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.45))
|
||||
start.press(forDuration: 0.8, thenDragTo: end)
|
||||
|
||||
app.waitForReaderState(containing: "selection=1", timeout: 8)
|
||||
app.waitForReaderState(containing: "toolbar=hidden", timeout: 3)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import XCTest
|
||||
|
||||
final class SettingsExtendedTests: XCTestCase {
|
||||
private let app = XCUIApplication()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
private func openSettings() {
|
||||
app.launchAndOpenSampleBook()
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
XCTAssertTrue(app.buttons[IDs.readerSettings].waitForExistence(timeout: 5))
|
||||
app.buttons[IDs.readerSettings].tap()
|
||||
XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5))
|
||||
}
|
||||
|
||||
func testBrightnessSliderExists() {
|
||||
openSettings()
|
||||
let slider = app.sliders[IDs.settingsBrightness]
|
||||
XCTAssertTrue(slider.waitForExistence(timeout: 3), "亮度滑块未出现")
|
||||
}
|
||||
|
||||
func testLineHeightControl() {
|
||||
openSettings()
|
||||
let lineHeight = app.segmentedControls[IDs.settingsLineHeight]
|
||||
XCTAssertTrue(lineHeight.waitForExistence(timeout: 3), "行距控件未出现")
|
||||
|
||||
lineHeight.buttons["紧凑"].tap()
|
||||
lineHeight.buttons["标准"].tap()
|
||||
lineHeight.buttons["宽松"].tap()
|
||||
|
||||
app.buttons[IDs.settingsDone].tap()
|
||||
XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5))
|
||||
}
|
||||
|
||||
func testColumnCountControl() {
|
||||
openSettings()
|
||||
let columns = app.segmentedControls[IDs.settingsColumns]
|
||||
XCTAssertTrue(columns.waitForExistence(timeout: 3), "分栏控件未出现")
|
||||
|
||||
columns.buttons["单栏"].tap()
|
||||
columns.buttons["双栏"].tap()
|
||||
|
||||
app.buttons[IDs.settingsDone].tap()
|
||||
XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5))
|
||||
}
|
||||
|
||||
func testDisplayTypeFromSettings() {
|
||||
openSettings()
|
||||
let displayType = app.segmentedControls[IDs.settingsDisplayType]
|
||||
XCTAssertTrue(displayType.waitForExistence(timeout: 3), "翻页方式控件未出现")
|
||||
|
||||
displayType.buttons["横滑"].tap()
|
||||
app.buttons[IDs.settingsDone].tap()
|
||||
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
|
||||
}
|
||||
|
||||
func testAllThemeButtonsExist() {
|
||||
openSettings()
|
||||
for index in 0...5 {
|
||||
let themeButton = app.buttons[IDs.settingsTheme(index)]
|
||||
XCTAssertTrue(themeButton.waitForExistence(timeout: 3), "主题按钮 \(index) 未出现")
|
||||
}
|
||||
}
|
||||
|
||||
func testScrollSettingsToReachAllControls() {
|
||||
openSettings()
|
||||
let scroll = app.scrollViews[IDs.settingsScroll]
|
||||
XCTAssertTrue(scroll.waitForExistence(timeout: 3))
|
||||
|
||||
XCTAssertTrue(app.sliders[IDs.settingsBrightness].waitForExistence(timeout: 3))
|
||||
XCTAssertTrue(app.segmentedControls[IDs.settingsLineHeight].waitForExistence(timeout: 3))
|
||||
XCTAssertTrue(app.segmentedControls[IDs.settingsColumns].waitForExistence(timeout: 3))
|
||||
XCTAssertTrue(app.segmentedControls[IDs.settingsDisplayType].waitForExistence(timeout: 3))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import XCTest
|
||||
|
||||
final class TableOfContentsTests: XCTestCase {
|
||||
private let app = XCUIApplication()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testTocButtonExists() {
|
||||
app.launchAndOpenSampleBook(bookTitleQuery: "宝山辽墓材料与释读")
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let tocButton = app.buttons[IDs.readerToc]
|
||||
XCTAssertTrue(tocButton.waitForExistence(timeout: 5), "目录按钮未出现")
|
||||
}
|
||||
|
||||
func testTocButtonTappable() {
|
||||
app.launchAndOpenSampleBook(bookTitleQuery: "宝山辽墓材料与释读")
|
||||
app.waitForReader()
|
||||
app.showReaderChromeIfNeeded()
|
||||
|
||||
let tocButton = app.buttons[IDs.readerToc]
|
||||
XCTAssertTrue(tocButton.waitForExistence(timeout: 5))
|
||||
XCTAssertTrue(tocButton.isEnabled, "目录按钮不可点击")
|
||||
tocButton.tap()
|
||||
|
||||
let tocPanel = app.tables[IDs.readerTocTable]
|
||||
XCTAssertTrue(tocPanel.waitForExistence(timeout: 5), "目录面板未出现")
|
||||
XCTAssertTrue(app.navigationBars[IDs.readerTocNavBar].waitForExistence(timeout: 2), "目录导航栏未暴露")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user