Skip to content

Add and Improve type guards for is methods #1037

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

Merged
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
19 changes: 14 additions & 5 deletions packages/core/src/graph-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Node}, `false` otherwise.
*/
function isNode (obj: object): obj is Node {
function isNode<
T extends NumberOrInteger = Integer,
P extends Properties = Properties,
Label extends string = string> (obj: unknown): obj is Node<T, P, Label> {
return hasIdentifierProperty(obj, NODE_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -228,7 +231,10 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Relationship}, `false` otherwise.
*/
function isRelationship (obj: object): obj is Relationship {
function isRelationship<
T extends NumberOrInteger = Integer,
P extends Properties = Properties,
Type extends string = string> (obj: unknown): obj is Relationship<T, P, Type> {
return hasIdentifierProperty(obj, RELATIONSHIP_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -346,7 +352,10 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link UnboundRelationship}, `false` otherwise.
*/
function isUnboundRelationship (obj: object): obj is UnboundRelationship {
function isUnboundRelationship<
T extends NumberOrInteger = Integer,
P extends Properties = Properties,
Type extends string = string> (obj: unknown): obj is UnboundRelationship<T, P, Type> {
return hasIdentifierProperty(obj, UNBOUND_RELATIONSHIP_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -394,7 +403,7 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link PathSegment}, `false` otherwise.
*/
function isPathSegment (obj: object): obj is PathSegment {
function isPathSegment<T extends NumberOrInteger = Integer> (obj: unknown): obj is PathSegment<T> {
return hasIdentifierProperty(obj, PATH_SEGMENT_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -448,7 +457,7 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Path}, `false` otherwise.
*/
function isPath (obj: object): obj is Path {
function isPath<T extends NumberOrInteger = Integer> (obj: unknown): obj is Path<T> {
return hasIdentifierProperty(obj, PATH_IDENTIFIER_PROPERTY)
}

Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/spatial-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Object.defineProperty(Point.prototype, POINT_IDENTIFIER_PROPERTY, {
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Point}, `false` otherwise.
*/
export function isPoint (obj?: any): obj is Point {
return obj != null && obj[POINT_IDENTIFIER_PROPERTY] === true
export function isPoint<T extends NumberOrInteger = Integer> (obj: unknown): obj is Point<T> {
const anyObj: any | null | undefined = obj
return obj != null && anyObj[POINT_IDENTIFIER_PROPERTY] === true
}
12 changes: 6 additions & 6 deletions packages/core/src/temporal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Duration}, `false` otherwise.
*/
export function isDuration (obj: object): obj is Duration {
export function isDuration<T extends NumberOrInteger = Integer> (obj: unknown): obj is Duration<T> {
return hasIdentifierProperty(obj, DURATION_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -206,7 +206,7 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link LocalTime}, `false` otherwise.
*/
export function isLocalTime (obj: object): boolean {
export function isLocalTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is LocalTime<T> {
return hasIdentifierProperty(obj, LOCAL_TIME_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -315,7 +315,7 @@ Object.defineProperty(
* @param {Object} obj the object to test.
* @return {boolean} `true` if given object is a {@link Time}, `false` otherwise.
*/
export function isTime (obj: object): obj is Time {
export function isTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is Time<T> {
return hasIdentifierProperty(obj, TIME_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -399,7 +399,7 @@ Object.defineProperty(
* @param {Object} obj - The object to test.
* @return {boolean} `true` if given object is a {@link Date}, `false` otherwise.
*/
export function isDate (obj: object): boolean {
export function isDate<T extends NumberOrInteger = Integer> (obj: unknown): obj is Date<T> {
return hasIdentifierProperty(obj, DATE_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -532,7 +532,7 @@ Object.defineProperty(
* @param {Object} obj - The object to test.
* @return {boolean} `true` if given object is a {@link LocalDateTime}, `false` otherwise.
*/
export function isLocalDateTime (obj: any): obj is LocalDateTime {
export function isLocalDateTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is LocalDateTime<T> {
return hasIdentifierProperty(obj, LOCAL_DATE_TIME_IDENTIFIER_PROPERTY)
}

Expand Down Expand Up @@ -733,7 +733,7 @@ Object.defineProperty(
* @param {Object} obj - The object to test.
* @return {boolean} `true` if given object is a {@link DateTime}, `false` otherwise.
*/
export function isDateTime (obj: object): boolean {
export function isDateTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is DateTime<T> {
return hasIdentifierProperty(obj, DATE_TIME_IDENTIFIER_PROPERTY)
}

Expand Down
185 changes: 182 additions & 3 deletions packages/core/test/graph-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
Relationship,
isRelationship,
UnboundRelationship,
isUnboundRelationship
isUnboundRelationship,
Path,
PathSegment,
isPath,
isPathSegment
} from '../src/graph-types'

import {
Expand Down Expand Up @@ -70,12 +74,30 @@ describe('Node', () => {
expect(node.toString()).toMatchSnapshot()
})

test.each(validNodes())('should be consider a node', (node: any) => {
test.each(validNodes())('should be consider a node', (node: unknown) => {
expect(isNode(node)).toBe(true)

if (isNode(node)) {
const typedNode: Node = node
expect(typedNode).toEqual(node)
} else {
// @ts-expect-error
const typedNode: Node = node
expect(typedNode).toEqual(node)
}
})

test.each(nonNodes())('should not consider a non-node object as node', nonNode => {
test.each(nonNodes())('should not consider a non-node object as node', (nonNode: unknown) => {
expect(isNode(nonNode)).toBe(false)

if (isNode(nonNode)) {
const typedNode: Node = nonNode
expect(typedNode).toEqual(nonNode)
} else {
// @ts-expect-error
const typedNode: Node = nonNode
expect(typedNode).toEqual(nonNode)
}
})

test('should type mapping labels', () => {
Expand Down Expand Up @@ -219,6 +241,32 @@ describe('Relationship', () => {
const _: 'DIRECTED' = a.type
})

test.each(validRelationships())('should be consider a relationship', (relationship: unknown) => {
expect(isRelationship(relationship)).toBe(true)

if (isRelationship(relationship)) {
const typedRelationship: Relationship = relationship
expect(typedRelationship).toEqual(relationship)
} else {
// @ts-expect-error
const typedRelationship: Relationship = relationship
expect(typedRelationship).toEqual(relationship)
}
})

test.each(nonRelationships())('should not consider a non-relationship object as relationship', (nonRelationship: unknown) => {
expect(isRelationship(nonRelationship)).toBe(false)

if (isRelationship(nonRelationship)) {
const typedRelationship: Relationship = nonRelationship
expect(typedRelationship).toEqual(nonRelationship)
} else {
// @ts-expect-error
const typedRelationship: Relationship = nonRelationship
expect(typedRelationship).toEqual(nonRelationship)
}
})

function validRelationships (): any[] {
return [
[new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId', 'endNodeElementId')],
Expand Down Expand Up @@ -346,6 +394,32 @@ describe('UnboundRelationship', () => {
const _: 'DIRECTED' = a.type
})

test.each(validUnboundRelationships())('should be consider a unbound relationship', (unboundRelationship: unknown) => {
expect(isUnboundRelationship(unboundRelationship)).toBe(true)

if (isUnboundRelationship(unboundRelationship)) {
const typedRelationship: UnboundRelationship = unboundRelationship
expect(typedRelationship).toEqual(unboundRelationship)
} else {
// @ts-expect-error
const typedRelationship: UnboundRelationship = unboundRelationship
expect(typedRelationship).toEqual(unboundRelationship)
}
})

test.each(nonUnboundRelationships())('should not consider a non-unbound relationship object as unbound relationship', (nonUnboundRelationship: unknown) => {
expect(isUnboundRelationship(nonUnboundRelationship)).toBe(false)

if (isUnboundRelationship(nonUnboundRelationship)) {
const typedRelationship: UnboundRelationship = nonUnboundRelationship
expect(typedRelationship).toEqual(nonUnboundRelationship)
} else {
// @ts-expect-error
const typedRelationship: UnboundRelationship = nonUnboundRelationship
expect(typedRelationship).toEqual(nonUnboundRelationship)
}
})

function validUnboundRelationships (): any[] {
return [
[new UnboundRelationship(1, 'Rel', {}, 'elementId')],
Expand Down Expand Up @@ -388,6 +462,111 @@ describe('UnboundRelationship', () => {
}
})

describe('Path', () => {
test.each(validPaths())('should be consider a path', (path: unknown) => {
expect(isPath(path)).toBe(true)

if (isPath(path)) {
const typed: Path = path
expect(typed).toEqual(path)
} else {
// @ts-expect-error
const typed: Path = path
expect(typed).toEqual(path)
}
})

test.each(nonPaths())('should not consider a non-path object as path', (nonPath: unknown) => {
expect(isPath(nonPath)).toBe(false)

if (isPath(nonPath)) {
const typed: Path = nonPath
expect(typed).toEqual(nonPath)
} else {
// @ts-expect-error
const typed: Path = nonPath
expect(typed).toEqual(nonPath)
}
})

function validPaths (): any[] {
return [
[new Path(new Node(1, [], {}), new Node(2, [], {}), [])],
[new Path(new Node(1, [], {}), new Node(2, [], {}), [new PathSegment(new Node(1, [], {}), new Relationship(1, 1, 2, 'type', {}), new Node(2, [], {}))])]
]
}

function nonPaths (): any[] {
return [
[{
start: new Node(1, [], {}),
end: new Node(2, [], {}),
length: 1,
segments: [
new PathSegment(
new Node(1, [], {}),
new Relationship(1, 1, 2, 'type', {}),
new Node(2, [], {}))
]
}],
[null],
[undefined],
[{}],
[1]
]
}
})

describe('Path', () => {
test.each(validPathSegments())('should be consider a path segment', (pathSegment: unknown) => {
expect(isPathSegment(pathSegment)).toBe(true)

if (isPathSegment(pathSegment)) {
const typed: PathSegment = pathSegment
expect(typed).toEqual(pathSegment)
} else {
// @ts-expect-error
const typed: PathSegment = pathSegment
expect(typed).toEqual(pathSegment)
}
})

test.each(nonPathSegments())('should not consider a non-path object as path segument', (nonPathSegment: unknown) => {
expect(isPathSegment(nonPathSegment)).toBe(false)

if (isPathSegment(nonPathSegment)) {
const typed: PathSegment = nonPathSegment
expect(typed).toEqual(nonPathSegment)
} else {
// @ts-expect-error
const typed: PathSegment = nonPathSegment
expect(typed).toEqual(nonPathSegment)
}
})

function validPathSegments (): any[] {
return [
[new PathSegment(new Node(1, [], {}), new Relationship(1, 1, 2, 'type', {}), new Node(2, [], {}))],
[new PathSegment(new Node(int(1), [], {}), new Relationship(int(1), int(1), int(2), 'type', {}), new Node(int(2), [], {}))]
]
}

function nonPathSegments (): any[] {
return [
[{

start: new Node(1, [], {}),
end: new Node(2, [], {}),
relationship: new Relationship(1, 1, 2, 'type', {})
}],
[null],
[undefined],
[{}],
[1]
]
}
})

function validIdentityAndExpectedElementIds (): any[] {
return [
[10, '10'],
Expand Down
28 changes: 24 additions & 4 deletions packages/core/test/spatial-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,34 @@ describe('isPoint', () => {
new Point(CARTESIAN_3D_CRS_CODE, 19.24, 100.29, 20.22222),
new Point(CARTESIAN_3D_CRS_CODE, 19.24, 100.29),
new Point(0, 19.24, 100.29, 20.22222)
])('isPoint(%s) should be truthy', point =>
expect(isPoint(point)).toBeTruthy()
)
])('isPoint(%s) should be true', (point: unknown) => {
expect(isPoint(point)).toBe(true)

if (isPoint(point)) {
const typedPoint: Point = point
expect(typedPoint).toEqual(point)
} else {
// @ts-expect-error
const typedPoint: Point = point
expect(typedPoint).toEqual(point)
}
})

test.each([
{ srid: CARTESIAN_3D_CRS_CODE, x: 18.24, y: 13.8, z: 124 },
{ srid: 0, x: 18.24, y: 13.8 },
['srid', CARTESIAN_3D_CRS_CODE, 'x', 18.24, 'y', 12.8, 'z', 124],
'Point(1, 2, 3, 4)'
])('isPoint(%s) should be falsy', point => expect(isPoint(point)).toBeFalsy())
])('isPoint(%s) should be false', (point: unknown) => {
expect(isPoint(point)).toBe(false)

if (isPoint(point)) {
const typedPoint: Point = point
expect(typedPoint).toEqual(point)
} else {
// @ts-expect-error
const typedPoint: Point = point
expect(typedPoint).toEqual(point)
}
})
})
Loading