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

Allow enums, identifiables, and nils for notEqual #28

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion PredicateKit/Predicate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,6 @@ public func <= <E: Expression, T: RawRepresentable> (lhs: E, rhs: T) -> Predicat
.comparison(.init(lhs, .lessThanOrEqual, rhs.rawValue))
}


public func == <E: Expression, T: Equatable & Primitive> (lhs: E, rhs: T) -> Predicate<E.Root> where E.Value == T {
.comparison(.init(lhs, .equal, rhs))
}
Expand All @@ -406,6 +405,20 @@ public func != <E: Expression, T: Equatable & Primitive> (lhs: E, rhs: T) -> Pre
.comparison(.init(lhs, .notEqual, rhs))
}

public func != <E: Expression, T: RawRepresentable> (lhs: E, rhs: T) -> Predicate<E.Root> where E.Value == T, T.RawValue: Equatable & Primitive {
.comparison(.init(lhs, .notEqual, rhs.rawValue))
}

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func != <E: Expression, T: Identifiable> (lhs: E, rhs: T) -> Predicate<E.Root> where E.Value == T, T.ID: Primitive {
.comparison(.init(ObjectIdentifier<E, T.ID>(root: lhs), .notEqual, rhs.id))
}

@_disfavoredOverload
public func != <E: Expression> (lhs: E, rhs: Nil) -> Predicate<E.Root> where E.Value: OptionalType {
.comparison(.init(lhs, .notEqual, rhs))
}

public func >= <E: Expression, T: Comparable & Primitive> (lhs: E, rhs: T) -> Predicate<E.Root> where E.Value == T {
.comparison(.init(lhs, .greaterThanOrEqual, rhs))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,61 @@ final class NSFetchRequestBuilderTests: XCTestCase {
XCTAssertEqual(comparison.comparisonPredicateModifier, .direct)
}

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func testNotEqualWithIdentifiable() throws {
guard let identifiable = makeIdentifiable() else {
XCTFail("could not initialize IdentifiableData")
return
}

identifiable.id = "42"

let request = makeRequest(\Data.identifiable != identifiable)
let builder = makeRequestBuilder()

let result: NSFetchRequest<Data> = builder.makeRequest(from: request)

let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate)
XCTAssertEqual(comparison.leftExpression, NSExpression(forKeyPath: "identifiable.id"))
XCTAssertEqual(comparison.rightExpression, NSExpression(forConstantValue: "42"))
XCTAssertEqual(comparison.predicateOperatorType, .notEqualTo)
XCTAssertEqual(comparison.comparisonPredicateModifier, .direct)
}

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func testNotEqualWithOptionalIdentifiable() throws {
guard let identifiable = makeIdentifiable() else {
XCTFail("could not initialize IdentifiableData")
return
}

identifiable.id = "42"

let request = makeRequest(\Data.optionalIdentifiable != identifiable)
let builder = makeRequestBuilder()

let result: NSFetchRequest<Data> = builder.makeRequest(from: request)

let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate)
XCTAssertEqual(comparison.leftExpression, NSExpression(forKeyPath: "optionalIdentifiable.id"))
XCTAssertEqual(comparison.rightExpression, NSExpression(forConstantValue: "42"))
XCTAssertEqual(comparison.predicateOperatorType, .notEqualTo)
XCTAssertEqual(comparison.comparisonPredicateModifier, .direct)
}

func testNotEqualWithRawRepresentable() throws {
let request = makeRequest(\Data.dataType != .two)
let builder = makeRequestBuilder()

let result: NSFetchRequest<Data> = builder.makeRequest(from: request)

let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate)
XCTAssertEqual(comparison.leftExpression, NSExpression(forKeyPath: "dataType"))
XCTAssertEqual(comparison.rightExpression, NSExpression(forConstantValue: DataType.two.rawValue))
XCTAssertEqual(comparison.predicateOperatorType, .notEqualTo)
XCTAssertEqual(comparison.comparisonPredicateModifier, .direct)
}

func testArrayElementNotEqualPredicate() throws {
let request = makeRequest((\Data.relationships).last(\.count) != 42)
let builder = makeRequestBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,27 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase {
XCTAssertEqual(notes.first?.numberOfViews, 42)
}

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func testFetchWithObjectComparison2() throws {
let attachment1 = try container.viewContext.insertAttachment("1")
let attachment2 = try container.viewContext.insertAttachment("2")

try container.viewContext.insertNotes(
(text: "Hello, World!", creationDate: Date(), numberOfViews: 42, tags: ["greeting"], attachment: attachment1 ),
(text: "Goodbye!", creationDate: Date(), numberOfViews: 3, tags: ["greeting"], attachment: attachment2 ),
(text: "See ya!", creationDate: Date(), numberOfViews: 3, tags: ["greeting"], attachment: attachment2 )
)

let notes: [Note] = try container.viewContext
.fetch(where: \Note.attachment != attachment2)
.result()

XCTAssertEqual(notes.count, 1)
XCTAssertEqual(notes.first?.text, "Hello, World!")
XCTAssertEqual(notes.first?.tags, ["greeting"])
XCTAssertEqual(notes.first?.numberOfViews, 42)
}

func testFetchWithEnumComparison() throws {
try container.viewContext.insertNotes(
(text: "Hello, World!", creationDate: Date(), numberOfViews: 42, tags: ["greeting"], type: .freeForm),
Expand All @@ -153,6 +174,22 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase {
XCTAssertEqual(notes.first?.numberOfViews, 42)
}

func testFetchWithEnumComparison2() throws {
try container.viewContext.insertNotes(
(text: "Hello, World!", creationDate: Date(), numberOfViews: 42, tags: ["greeting"], type: .freeForm),
(text: "Goodbye!", creationDate: Date(), numberOfViews: 122, tags: ["greeting"], type: .structured)
)

let notes: [Note] = try container.viewContext
.fetch(where: \Note.type != .structured)
.result()

XCTAssertEqual(notes.count, 1)
XCTAssertEqual(notes.first?.text, "Hello, World!")
XCTAssertEqual(notes.first?.tags, ["greeting"])
XCTAssertEqual(notes.first?.numberOfViews, 42)
}

func testFetchAll() throws {
try container.viewContext.insertNotes(
(text: "Hello, World!", creationDate: Date(), numberOfViews: 42, tags: ["greeting"]),
Expand Down Expand Up @@ -684,7 +721,25 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase {
XCTAssertEqual(notes.first?.numberOfViews, 3)
}

func testFetchWithArrayNilEqualityNilEquality() throws {
func testFetchWithNilInequality() throws {
let now = Date()

try container.viewContext.insertNotes(
(text: "Hello, World!", creationDate: .distantFuture, updateDate: now, numberOfViews: 42, tags: ["greeting"]),
(text: "Goodbye!", creationDate: .distantPast, updateDate: nil, numberOfViews: 3, tags: ["greeting"])
)

let notes: [Note] = try container.viewContext
.fetch(where: \Note.updateDate != nil)
.result()

XCTAssertEqual(notes.count, 1)
XCTAssertEqual(notes.first?.text, "Hello, World!")
XCTAssertEqual(notes.first?.tags, ["greeting"])
XCTAssertEqual(notes.first?.numberOfViews, 42)
}

func testFetchWithArrayNilEquality() throws {
try container.viewContext.insertUsers(
(name: "John Doe", billingAccountType: "Pro", purchases: [35.0, 120.0]),
(name: "Jane Doe", billingAccountType: "Default", purchases: nil)
Expand Down
129 changes: 129 additions & 0 deletions PredicateKitTests/OperatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,37 @@ final class OperatorTests: XCTestCase {
XCTAssertEqual(value, "1")
}

func testKeyPathEqualRawRepresentable() throws {
struct Data {
let rawRepresentable: RawRepresentableValue
}

enum RawRepresentableValue: Int {
case zero
case one
}

let predicate = \Data.rawRepresentable == .zero

guard case let .comparison(comparison) = predicate else {
XCTFail("rawRepresentable == .zero should result in a comparison")
return
}

guard
let expression = comparison.expression.as(KeyPath<Data, RawRepresentableValue>.self)
else {
XCTFail("the left side of the comparison should be a key path")
return
}

let value = try XCTUnwrap(comparison.value as? RawRepresentableValue.RawValue)

XCTAssertEqual(expression, \Data.rawRepresentable)
XCTAssertEqual(comparison.operator, .equal)
XCTAssertEqual(value, 0)
}

func testOptionalKeyPathEqualToNil() throws {
let predicate: Predicate<Data> = \Data.optionalRelationship == nil

Expand Down Expand Up @@ -472,6 +503,104 @@ final class OperatorTests: XCTestCase {
XCTAssertEqual(value, 5)
}

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func testKeyPathNotEqualIdentifiable() throws {
struct Data {
let identifiable: IdentifiableData
}

struct IdentifiableData: Identifiable, Equatable {
let id: String
}

let predicate = \Data.identifiable != IdentifiableData(id: "1")

guard case let .comparison(comparison) = predicate else {
XCTFail("identifiable.id != 1 should result in a comparison")
return
}

guard
let expression = comparison.expression.as(ObjectIdentifier<KeyPath<Data, IdentifiableData>, String>.self)
else {
XCTFail("the left side of the comparison should be a key path expression")
return
}

let value = try XCTUnwrap(comparison.value as? IdentifiableData.ID)

XCTAssertEqual(expression.root, \Data.identifiable)
XCTAssertEqual(comparison.operator, .notEqual)
XCTAssertEqual(value, "1")
}

func testKeyPathNotEqualRawRepresentable() throws {
struct Data {
let rawRepresentable: RawRepresentableValue
}

enum RawRepresentableValue: Int {
case zero
case one
}

let predicate = \Data.rawRepresentable != .zero

guard case let .comparison(comparison) = predicate else {
XCTFail("rawRepresentable != .zero should result in a comparison")
return
}

guard
let expression = comparison.expression.as(KeyPath<Data, RawRepresentableValue>.self)
else {
XCTFail("the left side of the comparison should be a key path")
return
}

let value = try XCTUnwrap(comparison.value as? RawRepresentableValue.RawValue)

XCTAssertEqual(expression, \Data.rawRepresentable)
XCTAssertEqual(comparison.operator, .notEqual)
XCTAssertEqual(value, 0)
}

func testOptionalKeyPathNotEqualToNil() throws {
let predicate: Predicate<Data> = \Data.optionalRelationship != nil

guard case let .comparison(comparison) = predicate else {
XCTFail("optionalRelationship != nil should result in a comparison")
return
}

guard let keyPath = comparison.expression.as(KeyPath<Data, Relationship?>.self) else {
XCTFail("the left side of the comparison should be a key path expression")
return
}

XCTAssertEqual(keyPath, \Data.optionalRelationship)
XCTAssertEqual(comparison.operator, .notEqual)
XCTAssertNotNil(comparison.value as? Nil)
}

func testOptionalArrayKeyPathNotEqualToNil() throws {
let predicate: Predicate<Data> = \Data.optionalRelationships != nil

guard case let .comparison(comparison) = predicate else {
XCTFail("optionalRelationships != nil should result in a comparison")
return
}

guard let keyPath = comparison.expression.as(KeyPath<Data, [Relationship]?>.self) else {
XCTFail("the left side of the comparison should be a key path expression")
return
}

XCTAssertEqual(keyPath, \Data.optionalRelationships)
XCTAssertEqual(comparison.operator, .notEqual)
XCTAssertNotNil(comparison.value as? Nil)
}

// MARK: - >=

func testKeyPathGreaterThanOrEqualPrimitive() throws {
Expand Down
Loading