Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blog service lookup blog #19366

Merged
merged 9 commits into from
Sep 29, 2022
Merged

Blog service lookup blog #19366

merged 9 commits into from
Sep 29, 2022

Conversation

crazytonyli
Copy link
Contributor

This PR moves some Blog history query functions from BlogService to a Blog extension.

Again, the new code (the extension) in this PR is translated from Objective-C code. I don't see big risk in it. Most changes in this PR are switching to call the new API. An outlier is e0ebe8f, I noticed the blog argument is not used anywhere, so I removed it, along with a Core Data query.

Regression Notes

  1. Potential unintended areas of impact
    None.

  2. What I did to test those areas of impact (or what existing automated tests I relied on)
    None.

  3. What automated tests I added (or what prevented me from doing so)
    None.

PR submission checklist:

  • I have completed the Regression Notes.
  • I have considered adding unit tests for my changes.
  • I have considered adding accessibility improvements for my changes.
  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

@crazytonyli crazytonyli added the Core Data Issues related to Core Data label Sep 28, 2022
@crazytonyli crazytonyli added this to the 20.9 milestone Sep 28, 2022
@crazytonyli crazytonyli requested a review from mokagio September 28, 2022 07:57
@crazytonyli crazytonyli self-assigned this Sep 28, 2022
blog(with: NSPredicate(format: "visible = YES"), in: context)
}

private static func blog(with predicate: NSPredicate, in context: NSManagedObjectContext) -> Blog? {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is translated from BlogService.blogsWithPredicate

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Sep 28, 2022

You can test the changes in Jetpack from this Pull Request by:
  • Clicking here or scanning the QR code below to access App Center
  • Then installing the build number pr19366-274e85d on your iPhone

If you need access to App Center, please ask a maintainer to add you.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Sep 28, 2022

You can test the changes in WordPress from this Pull Request by:
  • Clicking here or scanning the QR code below to access App Center
  • Then installing the build number pr19366-274e85d on your iPhone

If you need access to App Center, please ask a maintainer to add you.

Comment on lines +27 to +40
private static func blog(with predicate: NSPredicate, in context: NSManagedObjectContext) -> Blog? {
let request = NSFetchRequest<Blog>(entityName: NSStringFromClass(Blog.self))
request.includesSubentities = false
request.predicate = predicate
request.fetchLimit = 1
request.sortDescriptors = [NSSortDescriptor(key: "settings.name", ascending: true)]

do {
return try context.fetch(request).first
} catch {
DDLogError("Couldn't fetch blogs with predicate \(predicate): \(error)")
return nil
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in a future iteration it would be cool to throw or return Result<Blog, Error>.

Of course, that's out of scope now and also would require making a choice over which approach to use. Just thinking out loud.

Comment on lines -35 to -40
/**
Returns the blog currently flagged as the one last used, or the primary blog,
or the first blog in an alphanumerically sorted list that supports the given
feature, whichever is found first.
*/
- (nullable Blog *)lastUsedOrFirstBlogThatSupports:(BlogFeature)feature;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method hasn't been converted. But the code builds without it, so it was clearly unused 😄 👍

Comment on lines +145 to +154
private class MockViewController: UIViewController {

var presentExpectation: XCTestExpectation?

override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
presentExpectation?.fulfill()
super.present(viewControllerToPresent, animated: flag, completion: completion)
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see this being used in other tests, too. (I'm not actually sure there are other use cases, but I assume this mustn't be the only test calling present....

How would you feel about making it "stricter"? I.e. require the expectation to be set, as that's the whole point why one would use this UIViewController subclass.

It could be done by throwing if presentExpectation is nil. Or, we could provide a new, dedicated initializer, although, I always forget whether you can make the other ones private when working with UIViewController subclasses.

Another idea could be to move the whole expectation logic within the object, and make it expose a method to verify the presentation occurred. That would also be truer to the "mock" name, defined in the xunit patterns as a test double that verifies "indirect outputs of SUT" by verifying "correctness against expectations". (See my thoughts on test double naming here).

let presenterMock = PresenterMockViewController()
...
presenter.presentReblog(blogService: blogService!, readerPost: readerPost!, origin: presenterMock)
...
presenterMock.expectPresentationToOccurr()

// In PresenterMockViewController
func expectPresentationToOccurr(file: ... = #file, line: ... = #line) {
  guard let presentExpectation = self.presentExpectation else { /* fail */ }

  waitForExpectations([presentExpectation], timeout: ...) { error in
    guard let error = error else { return }
    XCTFail("present was not called", file: file, line: line)
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your thinking here—it should do what a "mock" type does if it's called a "mock". I tried to come up with some other ways to do this, like using OCMock(I couldn't get it to work in a Swift code), or capture the presented view controller and make assertions on it in the test function. But eventually I decided to go with what I had here, simply because that's the pattern used in the test class here (also a few other test classes that I ran into).

I do worry about having mixed patterns, especially if they are in the same file. Others(or myself in 6 months) may not know which pattern to follow when they make changes to this file. I'd prefer that we spend dedicate time to investigate alternative options than the "custom mock type with XCTExpectation" pattern used here, and update the whole test with the new pattern. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer that we spend dedicate time to investigate alternative options than the "custom mock type with XCTExpectation" pattern used here, and update the whole test [unit tests suite?] with the new pattern.

That would be very appropriate. It would also gives us a chance to see how the verify the different use cases.

Others(or myself in 6 months)

So true! I don't trust my future self's memory either 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #19373

/// Returns the blog currently flagged as the one last used, or the primary blog,
/// or the first blog in an alphanumerically sorted list, whichever is found first.
@objc(lastUsedOrFirstBlogInContext:)
static func lastUsedOrFirstBlog(in context: NSManagedObjectContext) -> Blog? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think this while reading the method name, but, after seeing this in action, I wonder if we can do without the "Blog".

Without it, the usage would be:

Blog.lastUsedOrFirst(in: context)

That reads fine to me, and it seems pretty clear that the return value would be a Blog.

What do you think? I'm not too bothered by it, just an idea. But, if you like the suggestion, I think we should document it somewhere as the desired style for these ActiveRecord-like methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. The "Blog" does seem redundant. 👍

Copy link
Contributor

@mokagio mokagio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-iterating my approval.

There were a couple of build steps that failed but I don't think they were related to these changes. I restarted them.

@crazytonyli crazytonyli merged commit 7624caf into trunk Sep 29, 2022
@crazytonyli crazytonyli deleted the blog-service-lookup-blog branch September 29, 2022 04:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Core Data Issues related to Core Data
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants