From 953cbfaac242e02abbed1dbf11f3ad5eaec26342 Mon Sep 17 00:00:00 2001 From: Robin van Boven <497556+Beanow@users.noreply.github.com> Date: Tue, 7 Jan 2020 13:39:21 +0100 Subject: [PATCH] UniRef: add CascadingReferenceDetector implementation (#1510) The CascadingReferenceDetector is an abstraction we can use for combining multiple ReferenceDetector instances and giving them a priority. --- .../references/cascadingReferenceDetector.js | 23 +++++ .../cascadingReferenceDetector.test.js | 85 +++++++++++++++++++ src/core/references/index.js | 1 + 3 files changed, 109 insertions(+) create mode 100644 src/core/references/cascadingReferenceDetector.js create mode 100644 src/core/references/cascadingReferenceDetector.test.js diff --git a/src/core/references/cascadingReferenceDetector.js b/src/core/references/cascadingReferenceDetector.js new file mode 100644 index 0000000..a98d3b2 --- /dev/null +++ b/src/core/references/cascadingReferenceDetector.js @@ -0,0 +1,23 @@ +//@flow + +import type {NodeAddressT} from "../graph"; +import type {ReferenceDetector, URL} from "./referenceDetector"; + +/** + * A class for composing ReferenceDetectors. Calls ReferenceDetectors in the order + * they're given in the constructor, returning the first NodeAddressT it encounters. + */ +export class CascadingReferenceDetector implements ReferenceDetector { + refs: $ReadOnlyArray; + + constructor(refs: $ReadOnlyArray) { + this.refs = refs; + } + + addressFromUrl(url: URL): ?NodeAddressT { + for (const ref of this.refs) { + const addr = ref.addressFromUrl(url); + if (addr) return addr; + } + } +} diff --git a/src/core/references/cascadingReferenceDetector.test.js b/src/core/references/cascadingReferenceDetector.test.js new file mode 100644 index 0000000..d3a116b --- /dev/null +++ b/src/core/references/cascadingReferenceDetector.test.js @@ -0,0 +1,85 @@ +// @flow + +import {type NodeAddressT, NodeAddress} from "../graph"; +import type {URL, ReferenceDetector} from "./referenceDetector"; +import {MappedReferenceDetector} from "./mappedReferenceDetector"; +import {CascadingReferenceDetector} from "./cascadingReferenceDetector"; + +const nodeA = NodeAddress.fromParts(["test", "A"]); +const nodeB = NodeAddress.fromParts(["test", "B"]); +const nodeC = NodeAddress.fromParts(["test", "C"]); + +function exampleDetector(pairs?: [URL, NodeAddressT][]): ReferenceDetector { + const map: Map = new Map(pairs); + const refs = new MappedReferenceDetector(map); + jest.spyOn(refs, "addressFromUrl"); + return refs; +} + +describe("core/references/cascadingReferenceDetector", () => { + describe("CascadingReferenceDetector", () => { + it("should implement the ReferenceDetector interface", () => { + const _unused_toReferenceDetector = ( + x: CascadingReferenceDetector + ): ReferenceDetector => x; + }); + + it("should try all ReferenceDetectors to look for a hit", () => { + // Given + const refs1 = exampleDetector(); + const refs2 = exampleDetector(); + const refs3 = exampleDetector(); + + // When + const refs = new CascadingReferenceDetector([refs1, refs2, refs3]); + const n1 = refs.addressFromUrl("http://foo.bar/miss"); + + // Then + expect(refs1.addressFromUrl.mock.calls).toEqual([ + ["http://foo.bar/miss"], + ]); + expect(refs2.addressFromUrl.mock.calls).toEqual([ + ["http://foo.bar/miss"], + ]); + expect(refs3.addressFromUrl.mock.calls).toEqual([ + ["http://foo.bar/miss"], + ]); + expect(n1).toEqual(undefined); + }); + + it("should return the first ReferenceDetector's value that provides a hit", () => { + // Given + const refs1 = exampleDetector([["http://foo.bar/1", nodeA]]); + const refs2 = exampleDetector([ + ["http://foo.bar/1", nodeB], + ["http://foo.bar/2", nodeB], + ]); + const refs3 = exampleDetector([ + ["http://foo.bar/1", nodeC], + ["http://foo.bar/2", nodeC], + ["http://foo.bar/3", nodeC], + ]); + + // When + const refs = new CascadingReferenceDetector([refs1, refs2, refs3]); + const n1 = refs.addressFromUrl("http://foo.bar/1"); + const n2 = refs.addressFromUrl("http://foo.bar/2"); + const n3 = refs.addressFromUrl("http://foo.bar/3"); + + // Then + expect(refs1.addressFromUrl.mock.calls).toEqual([ + ["http://foo.bar/1"], + ["http://foo.bar/2"], + ["http://foo.bar/3"], + ]); + expect(refs2.addressFromUrl.mock.calls).toEqual([ + ["http://foo.bar/2"], + ["http://foo.bar/3"], + ]); + expect(refs3.addressFromUrl.mock.calls).toEqual([["http://foo.bar/3"]]); + expect(n1).toEqual(nodeA); + expect(n2).toEqual(nodeB); + expect(n3).toEqual(nodeC); + }); + }); +}); diff --git a/src/core/references/index.js b/src/core/references/index.js index 03c94b6..50a3d54 100644 --- a/src/core/references/index.js +++ b/src/core/references/index.js @@ -2,3 +2,4 @@ export type {URL, ReferenceDetector} from "./referenceDetector"; export {MappedReferenceDetector} from "./mappedReferenceDetector"; +export {CascadingReferenceDetector} from "./cascadingReferenceDetector";