diff --git a/Example/AztecExample.xcodeproj/project.pbxproj b/Example/AztecExample.xcodeproj/project.pbxproj index a66a7bbf9..fb57f8ef4 100644 --- a/Example/AztecExample.xcodeproj/project.pbxproj +++ b/Example/AztecExample.xcodeproj/project.pbxproj @@ -22,6 +22,9 @@ B570B1CC1E82D343008CF41E /* CommentAttachmentRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B570B1CB1E82D343008CF41E /* CommentAttachmentRenderer.swift */; }; B5AF89341E93ECE60051EFDB /* HTMLAttachmentRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AF89331E93ECE60051EFDB /* HTMLAttachmentRenderer.swift */; }; B5DB1C371EC630E10005E623 /* UnknownEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DB1C361EC630E10005E623 /* UnknownEditorViewController.swift */; }; + BE6AF9551FB9EA30003846F9 /* BlogsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6AF9541FB9EA30003846F9 /* BlogsPage.swift */; }; + BE88E4651FB5BA5000F83E61 /* HighPriorityIssuesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE88E4621FB5BA5000F83E61 /* HighPriorityIssuesTests.swift */; }; + BE8EAC701FC1DE34005CD7D0 /* BasePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE8EAC6F1FC1DE34005CD7D0 /* BasePage.swift */; }; CC400F1A1E9EC04200859AB4 /* AztecUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC400F191E9EC04200859AB4 /* AztecUITests.swift */; }; CC400F251E9EC16900859AB4 /* XCTest+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC400F241E9EC16900859AB4 /* XCTest+Extensions.swift */; }; E63EF92B1D36A60B00B5BA4B /* EditorDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E63EF92A1D36A60B00B5BA4B /* EditorDemoController.swift */; }; @@ -117,6 +120,9 @@ B570B1CB1E82D343008CF41E /* CommentAttachmentRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentAttachmentRenderer.swift; sourceTree = ""; }; B5AF89331E93ECE60051EFDB /* HTMLAttachmentRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLAttachmentRenderer.swift; sourceTree = ""; }; B5DB1C361EC630E10005E623 /* UnknownEditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnknownEditorViewController.swift; sourceTree = ""; }; + BE6AF9541FB9EA30003846F9 /* BlogsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogsPage.swift; sourceTree = ""; }; + BE88E4621FB5BA5000F83E61 /* HighPriorityIssuesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighPriorityIssuesTests.swift; sourceTree = ""; }; + BE8EAC6F1FC1DE34005CD7D0 /* BasePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasePage.swift; sourceTree = ""; }; CC400F171E9EC04200859AB4 /* AztecUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AztecUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CC400F191E9EC04200859AB4 /* AztecUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AztecUITests.swift; sourceTree = ""; }; CC400F1B1E9EC04200859AB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -272,9 +278,20 @@ name = Renders; sourceTree = ""; }; + BE8EAC6A1FC1DCF3005CD7D0 /* Pages */ = { + isa = PBXGroup; + children = ( + BE6AF9541FB9EA30003846F9 /* BlogsPage.swift */, + BE8EAC6F1FC1DE34005CD7D0 /* BasePage.swift */, + ); + path = Pages; + sourceTree = ""; + }; CC400F181E9EC04200859AB4 /* AztecUITests */ = { isa = PBXGroup; children = ( + BE8EAC6A1FC1DCF3005CD7D0 /* Pages */, + BE88E4621FB5BA5000F83E61 /* HighPriorityIssuesTests.swift */, CC400F191E9EC04200859AB4 /* AztecUITests.swift */, CC400F1B1E9EC04200859AB4 /* Info.plist */, CC400F241E9EC16900859AB4 /* XCTest+Extensions.swift */, @@ -491,7 +508,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BE88E4651FB5BA5000F83E61 /* HighPriorityIssuesTests.swift in Sources */, + BE8EAC701FC1DE34005CD7D0 /* BasePage.swift in Sources */, CC400F251E9EC16900859AB4 /* XCTest+Extensions.swift in Sources */, + BE6AF9551FB9EA30003846F9 /* BlogsPage.swift in Sources */, CC400F1A1E9EC04200859AB4 /* AztecUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme b/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme index 87b6de084..619745ff9 100644 --- a/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme +++ b/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme @@ -63,6 +63,16 @@ ReferencedContainer = "container:AztecExample.xcodeproj"> + + + + tag, see #818. + Commenting these out because they fail. Should not be wrapped in a

tag, see #818. func testMoreTag() { app.scrollViews.otherElements.buttons[elementStringIDs.moreButton].tap() diff --git a/Example/AztecUITests/HighPriorityIssuesTests.swift b/Example/AztecUITests/HighPriorityIssuesTests.swift new file mode 100644 index 000000000..cd3a739ec --- /dev/null +++ b/Example/AztecUITests/HighPriorityIssuesTests.swift @@ -0,0 +1,128 @@ +import XCTest + +class HighPriorityIssuesTests: XCTestCase { + + private var app: XCUIApplication! + private var richTextField: XCUIElement! + + override func setUp() { + super.setUp() + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIDevice.shared().orientation = .portrait + app = XCUIApplication() + app.launch() + + let blogsPage = BlogsPage.init(appInstance: app) + blogsPage.gotoEmptyDemo() + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + // Github issue https://github.com/wordpress-mobile/AztecEditor-iOS/issues/385 + func testLongTitle() { + // Title line height is about 22px, so it might be useing for comparing the height difference should make it precise. + // But may be fragile due to different font sizes etc + let titleLineHeight = 22 + let titleTextView = app.textViews[elementStringIDs.titleTextField] + titleTextView.tap() + let oneLineTitleHeight = Int(titleTextView.frame.height) + + // TODO: Move it into EditorPage + if (app.windows.element(boundBy: 0).horizontalSizeClass == .compact || app.windows.element(boundBy: 0).verticalSizeClass == .compact) { + titleTextView.typeText("very very very very very very long title in a galaxy not so far away") + } else { + titleTextView.typeText("very very very very very very long title in a galaxy not so far away very very very very very very long title in a galaxy not so far away") + } + + let twoLineTitleHeight = Int(titleTextView.frame.height) + XCTAssert(twoLineTitleHeight - oneLineTitleHeight == titleLineHeight ) // XCTAssert(oneLineTitleHeight < twoLineTitleHeight) + } + + // Github issue https://github.com/wordpress-mobile/AztecEditor-iOS/issues/675 + func testInfiniteLoopOnAssetDownload() { + switchContentView() + enterTextInHTML(text: "") + switchContentView() + gotoRootPage() + + let editorDemoButton = app.tables.staticTexts[elementStringIDs.emptyDemo] + XCTAssert(editorDemoButton.exists, "Editor button not hittable. Are you on the right page?") + } + + // Github issue https://github.com/wordpress-mobile/AztecEditor-iOS/issues/465 + func testTypeAfterInvalidHTML() { + switchContentView() + enterTextInHTML(text: "") + switchContentView() + + let field = app.textViews[elementStringIDs.richTextField] + // Some magic to move caret to end of the text + let vector = CGVector(dx:field.frame.width, dy:field.frame.height - field.frame.minY) + field.coordinate(withNormalizedOffset:CGVector.zero).withOffset(vector).tap() + enterTextInField(text: "Some text after invalid HTML tag") + + let text = getHTMLContent() + XCTAssertEqual(text, "

Some text after invalid HTML tag

") + } + + // Github issue https://github.com/wordpress-mobile/AztecEditor-iOS/issues/768 + func testLooseStylesNoContent() { + let boldButton = app.scrollViews.otherElements.buttons[elementStringIDs.boldButton] + let italicButton = app.scrollViews.otherElements.buttons[elementStringIDs.italicButton] + + XCTAssert(!boldButton.isSelected && !italicButton.isSelected) + boldButton.tap() + italicButton.tap() + + enterTextInField(text: "q") + let deleteButton = app.keys["delete"] + deleteButton.tap() + deleteButton.tap() + XCTAssert(boldButton.isSelected && italicButton.isSelected) + } + + // Github issue https://github.com/wordpress-mobile/AztecEditor-iOS/issues/771 + func testCopyPasteCrash() { +// gotoRootPage() +// let blogsPage = BlogsPage.init(appInstance: app) +// blogsPage.gotoDemo() + + let text = "

Sample HTML content

this is some text that is spread out across several lines but is rendered on a single line in a browser

Character Styles

Bold text
Italic text
Underlined text
Strikethrough
Colors
Alternative underline text
I'm a link!
Text after the more break

Lists

Unordered List:

  • One
  • Two
  • Three

Ordered List:

  1. One
  2. Two
  3. Three


" + + switchContentView() +// selectAllTextInHTMLField() + enterTextInHTML(text: text) + + let htmlContentView = app.textViews[elementStringIDs.htmlTextField] +// let text = htmlContentView.value as! String + + selectAllTextInHTMLField() + app.menuItems[elementStringIDs.copyButton].tap() + htmlContentView.swipeUp() + htmlContentView.swipeUp() + htmlContentView.swipeUp() + + // determinating where to click to put caret to end of text + let frame = htmlContentView.frame + let buttonFrame = app.scrollViews.otherElements.buttons[elementStringIDs.mediaButton].frame.height + let vector = CGVector(dx: frame.width, dy: frame.height - (buttonFrame + 1)) + + htmlContentView.coordinate(withNormalizedOffset:CGVector.zero).withOffset(vector).tap() + htmlContentView.typeText("\n\n") + htmlContentView.tap() + app.menuItems[elementStringIDs.pasteButton].tap() + + sleep(3) // to make sure everything is updated + let newText = htmlContentView.value as! String + + XCTAssertEqual(newText, text + "\n\n" + text) + } +} + + diff --git a/Example/AztecUITests/Pages/BasePage.swift b/Example/AztecUITests/Pages/BasePage.swift new file mode 100644 index 000000000..c8a941df9 --- /dev/null +++ b/Example/AztecUITests/Pages/BasePage.swift @@ -0,0 +1,23 @@ +import Foundation +import XCTest + +class BasePage { + var app: XCUIApplication! + private var expectedElement: XCUIElement! + var waitTimeout: Double! + + init(appInstance: XCUIApplication, element: XCUIElement) { + app = appInstance + expectedElement = element + waitTimeout = 20 + waitForPage() + } + + func waitForPage() { + expectedElement.waitForExistence(timeout: waitTimeout) + } + + func isLoaded() -> Bool { + return expectedElement.exists + } +} diff --git a/Example/AztecUITests/Pages/BlogsPage.swift b/Example/AztecUITests/Pages/BlogsPage.swift new file mode 100644 index 000000000..8ef4d9bf8 --- /dev/null +++ b/Example/AztecUITests/Pages/BlogsPage.swift @@ -0,0 +1,37 @@ +import Foundation +import XCTest + + +class BlogsPage: BasePage { + + init(appInstance: XCUIApplication) { + let expectedElement = appInstance.tables.staticTexts[elementStringIDs.emptyDemo] + super.init(appInstance: appInstance, element: expectedElement) + } + + func gotoEmptyDemo() { + app.staticTexts[elementStringIDs.emptyDemo].tap() + + showOptionsStrip() + } + + func gotoDemo() { + app.staticTexts[elementStringIDs.demo].tap() + + showOptionsStrip() + } + + func showOptionsStrip() -> Void { + app.textViews[elementStringIDs.richTextField].tap() + expandOptionsSctrip() + } + + func expandOptionsSctrip() -> Void { + let expandButton = app.children(matching: .window).element(boundBy: 1).children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .button).element + let htmlButton = app.scrollViews.otherElements.buttons[elementStringIDs.sourcecodeButton] + + if expandButton.exists && expandButton.isHittable && !htmlButton.exists { + expandButton.tap() + } + } +} diff --git a/Example/AztecUITests/XCTest+Extensions.swift b/Example/AztecUITests/XCTest+Extensions.swift index f2f705509..450443090 100644 --- a/Example/AztecUITests/XCTest+Extensions.swift +++ b/Example/AztecUITests/XCTest+Extensions.swift @@ -1,10 +1,12 @@ -import XCTest + import XCTest public struct elementStringIDs { // Demo Menu static var emptyDemo = "Empty Editor Demo" + static var demo = "Editor Demo" // Text Fields + static var titleTextField = "Title" static var richTextField = "richContentView" static var htmlTextField = "HTMLContentView" @@ -35,18 +37,46 @@ public struct elementStringIDs { static var header4Button = "Heading 4" static var header5Button = "Heading 5" static var header6Button = "Heading 6" + + // Menu items + static var copyButton = "Copy" + static var pasteButton = "Paste" + } extension XCTest { + /** + Common method to type in different text fields + */ + func typeToTextField(text: String, to: String) -> Void { + let app = XCUIApplication() + let textField = app.textViews[to] + + textField.typeText(text) + } + /** Enters text in the rich text field with auto-correction disabled - Parameter text: the test to enter into the field */ func enterTextInField(text: String) -> Void { - let app = XCUIApplication() - let richTextField = app.textViews[elementStringIDs.richTextField] - - richTextField.typeText(text) + typeToTextField(text: text, to: elementStringIDs.richTextField) + } + + /** + Enters text into title field. + - Parameter text: the test to enter into the title + */ + func enterTextInTitle(text: String) -> Void { + typeToTextField(text: text, to: elementStringIDs.titleTextField) + } + + /** + Enters text into HTML field. + - Parameter text: the test to enter into the title + */ + func enterTextInHTML(text: String) -> Void { + typeToTextField(text: text, to: elementStringIDs.htmlTextField) } /** @@ -55,7 +85,26 @@ extension XCTest { func selectAllTextInField() -> Void { let app = XCUIApplication() let richTextField = app.textViews[elementStringIDs.richTextField] + + richTextField.press(forDuration: 1.2) + app.menuItems.element(boundBy: 1).tap() + } + + /** + Selects all entered text in the rich text field + */ + func selectAllTextInHTMLField() -> Void { + selectAllText(field: elementStringIDs.htmlTextField) + } + + /** + Selects all entered text in provided textView element + */ + func selectAllText(field: String) -> Void { + let app = XCUIApplication() + let richTextField = app.textViews[field] + richTextField.press(forDuration: 1.2) app.menuItems.element(boundBy: 1).tap() } @@ -70,10 +119,11 @@ extension XCTest { let elementsQuery = app.scrollViews.otherElements let htmlButton = elementsQuery.buttons[elementStringIDs.sourcecodeButton] if (!htmlButton.isHittable) { - elementsQuery.buttons[elementStringIDs.mediaButton].swipeLeft() + elementsQuery.buttons[elementStringIDs.linkButton].swipeLeft() } htmlButton.tap() + let htmlContentTextView = app.textViews[elementStringIDs.htmlTextField] let text = htmlContentTextView.value as! String @@ -81,8 +131,43 @@ extension XCTest { // Remove spaces between HTML tags. let regex = try! NSRegularExpression(pattern: ">\\s+?<", options: .caseInsensitive) let range = NSMakeRange(0, text.count) - let strippedText = regex.stringByReplacingMatches(in: text, options: .reportCompletion, range: range, withTemplate: "><") - + let strippedText = regex.stringByReplacingMatches(in: text, options: .reportCompletion, range: range, withTemplate: "><") + return strippedText } + + func getRichTextContent() -> String { + let app = XCUIApplication() + + let richContentTextView = app.textViews[elementStringIDs.richTextField] + let text = richContentTextView.value as! String + return text + } + + /** + Switch Content view between Rich text & HTML + */ + func switchContentView() -> Void { + toolbarButtonTap(locator: elementStringIDs.sourcecodeButton) + } + + /** + Tapping on toolbar button. And swipes if needed. + */ + func toolbarButtonTap(locator: String) { + let elementsQuery = XCUIApplication().scrollViews.otherElements + let button = elementsQuery.buttons[locator] + let swipeElement = elementsQuery.buttons[elementStringIDs.mediaButton].isHittable ? elementsQuery.buttons[elementStringIDs.mediaButton] : elementsQuery.buttons[elementStringIDs.linkButton] + + if !button.exists || !button.isHittable { + swipeElement.swipeLeft() + } + button.tap() + } + + func gotoRootPage() -> Void { + let app = XCUIApplication() + + return app.navigationBars["AztecExample.EditorDemo"].buttons["Root View Controller"].tap() + } }