diff --git a/src/core/references/index.js b/src/core/references/index.js index 50a3d54..1abb87d 100644 --- a/src/core/references/index.js +++ b/src/core/references/index.js @@ -3,3 +3,4 @@ export type {URL, ReferenceDetector} from "./referenceDetector"; export {MappedReferenceDetector} from "./mappedReferenceDetector"; export {CascadingReferenceDetector} from "./cascadingReferenceDetector"; +export {TranslatingReferenceDetector} from "./translatingReferenceDetector"; diff --git a/src/core/references/translatingReferenceDetector.js b/src/core/references/translatingReferenceDetector.js new file mode 100644 index 0000000..bdef6b4 --- /dev/null +++ b/src/core/references/translatingReferenceDetector.js @@ -0,0 +1,26 @@ +// @flow + +import type {NodeAddressT} from "../graph"; +import type {ReferenceDetector, URL} from "./referenceDetector"; + +type TranslateFunction = (NodeAddressT) => ?NodeAddressT; + +/** + * A ReferenceDetector which takes a base ReferenceDetector and applies a + * translate function to any results. + */ +export class TranslatingReferenceDetector implements ReferenceDetector { + translate: TranslateFunction; + base: ReferenceDetector; + + constructor(base: ReferenceDetector, translate: TranslateFunction) { + this.base = base; + this.translate = translate; + } + + addressFromUrl(url: URL): ?NodeAddressT { + const baseAddr = this.base.addressFromUrl(url); + if (!baseAddr) return; + return this.translate(baseAddr); + } +} diff --git a/src/core/references/translatingReferenceDetector.test.js b/src/core/references/translatingReferenceDetector.test.js new file mode 100644 index 0000000..ea7a496 --- /dev/null +++ b/src/core/references/translatingReferenceDetector.test.js @@ -0,0 +1,56 @@ +// @flow + +import {type NodeAddressT, NodeAddress} from "../graph"; +import type {URL, ReferenceDetector} from "./referenceDetector"; +import {MappedReferenceDetector} from "./mappedReferenceDetector"; +import {TranslatingReferenceDetector} from "./translatingReferenceDetector"; + +const nodeA = NodeAddress.fromParts(["test", "A"]); +const nodeB = NodeAddress.fromParts(["test", "B"]); + +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/translatingReferenceDetector", () => { + describe("TranslatingReferenceDetector", () => { + it("should implement the ReferenceDetector interface", () => { + const _unused_toReferenceDetector = ( + x: TranslatingReferenceDetector + ): ReferenceDetector => x; + }); + + it("should not call translate function when base has no hit", () => { + // Given + const base = exampleDetector(); + const translate = jest.fn().mockImplementation(() => nodeB); + + // When + const refs = new TranslatingReferenceDetector(base, translate); + const n1 = refs.addressFromUrl("http://foo.bar/miss"); + + // Then + expect(base.addressFromUrl).toBeCalledWith("http://foo.bar/miss"); + expect(translate).toBeCalledTimes(0); + expect(n1).toEqual(undefined); + }); + + it("should call translate function when base has a hit", () => { + // Given + const base = exampleDetector([["http://foo.bar/a", nodeA]]); + const translate = jest.fn().mockImplementation(() => nodeB); + + // When + const refs = new TranslatingReferenceDetector(base, translate); + const n1 = refs.addressFromUrl("http://foo.bar/a"); + + // Then + expect(base.addressFromUrl).toBeCalledWith("http://foo.bar/a"); + expect(translate).toBeCalledWith(nodeA); + expect(n1).toEqual(nodeB); + }); + }); +});