From 0a6fb37322644106a9ab1015c40b835982fa5ed7 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 15 Feb 2018 03:04:03 +0100 Subject: [PATCH] node tracking - add tests (#4045) * move node tracking code to separate dedicated file * Add jsdoc for findRootNode * add tests for node tracking --- packages/gatsby/src/redux/actions.js | 7 +- packages/gatsby/src/redux/index.js | 46 ------ .../schema/__tests__/node-tracking-test.js | 138 ++++++++++++++++++ packages/gatsby/src/schema/node-tracking.js | 81 ++++++++++ packages/gatsby/src/schema/run-sift.js | 2 +- packages/gatsby/src/schema/types/type-file.js | 30 +--- 6 files changed, 224 insertions(+), 80 deletions(-) create mode 100644 packages/gatsby/src/schema/__tests__/node-tracking-test.js create mode 100644 packages/gatsby/src/schema/node-tracking.js diff --git a/packages/gatsby/src/redux/actions.js b/packages/gatsby/src/redux/actions.js index 3fc1475f729e5..e7528c26757bd 100644 --- a/packages/gatsby/src/redux/actions.js +++ b/packages/gatsby/src/redux/actions.js @@ -8,11 +8,8 @@ const glob = require(`glob`) const path = require(`path`) const fs = require(`fs`) const { joinPath } = require(`../utils/path`) -const { - getNode, - hasNodeChanged, - trackInlineObjectsInRootNode, -} = require(`./index`) +const { getNode, hasNodeChanged } = require(`./index`) +const { trackInlineObjectsInRootNode } = require(`../schema/node-tracking`) const { store } = require(`./index`) import * as joiSchemas from "../joi-schemas/joi" import { generateComponentChunkName } from "../utils/js-chunk-names" diff --git a/packages/gatsby/src/redux/index.js b/packages/gatsby/src/redux/index.js index 64d6fce0d09ce..622fd09f6ec9e 100644 --- a/packages/gatsby/src/redux/index.js +++ b/packages/gatsby/src/redux/index.js @@ -12,56 +12,12 @@ const emitter = mitt() // Reducers const reducers = require(`./reducers`) -// Root node tracking - -/** - * Map containing links between inline objects or arrays - * and Node that contains them - * @type {Object.<(Object|Array),string>} - */ -const rootNodeMap = new WeakMap() - -/** - * Add link between passed data and Node. This function shouldn't be used - * directly. Use higher level `trackInlineObjectsInRootNode` - * @see trackInlineObjectsInRootNode - * @param {(Object|Array)} data Inline object or array - * @param {string} nodeId Id of node that contains data passed in first parameter - */ -const addRootNodeToInlineObject = (data, nodeId) => { - if (_.isPlainObject(data) || _.isArray(data)) { - _.each(data, o => addRootNodeToInlineObject(o, nodeId)) - rootNodeMap.set(data, nodeId) - } -} - -/** - * Adds link between inline objects/arrays contained in Node object - * and that Node object. - * @param {Node} node Root Node - */ -const trackInlineObjectsInRootNode = node => { - _.each(node, (v, k) => { - // Ignore the node internal object. - if (k === `internal`) { - return - } - addRootNodeToInlineObject(v, node.id) - }) - return node -} -exports.trackInlineObjectsInRootNode = trackInlineObjectsInRootNode - // Read from cache the old node data. let initialState = {} try { initialState = JSON.parse( fs.readFileSync(`${process.cwd()}/.cache/redux-state.json`) ) - - _.each(initialState.nodes, node => { - trackInlineObjectsInRootNode(node) - }) } catch (e) { // ignore errors. } @@ -158,8 +114,6 @@ exports.getNodeAndSavePathDependency = (id, path) => { return node } -exports.getRootNodeId = node => rootNodeMap.get(node) - // Start plugin runner which listens to the store // and invokes Gatsby API based on actions. require(`./plugin-runner`) diff --git a/packages/gatsby/src/schema/__tests__/node-tracking-test.js b/packages/gatsby/src/schema/__tests__/node-tracking-test.js new file mode 100644 index 0000000000000..2fdb575dbd665 --- /dev/null +++ b/packages/gatsby/src/schema/__tests__/node-tracking-test.js @@ -0,0 +1,138 @@ +jest.mock(`fs`) + +describe(`Track root nodes`, () => { + const reduxStatePath = `${process.cwd()}/.cache/redux-state.json` + const MOCK_FILE_INFO = {} + MOCK_FILE_INFO[reduxStatePath] = ` + { + "nodes": { + "id1": { + "id": "id1", + "parent": null, + "children": [], + "inlineObject": { + "field": "fieldOfFirstNode" + }, + "inlineArray": [ + 1, 2, 3 + ], + "internal": { + "type": "TestNode", + "contentDigest": "digest1", + "owner": "test" + } + } + } + } + ` + require(`fs`).__setMockFiles(MOCK_FILE_INFO) + + const { getNode, getNodes } = require(`../../redux`) + const { findRootNode } = require(`../node-tracking`) + const runSift = require(`../run-sift`) + const buildNodeTypes = require(`../build-node-types`) + const { boundActionCreators: { createNode } } = require(`../../redux/actions`) + + createNode( + { + id: `id2`, + parent: null, + children: [], + inlineObject: { + field: `fieldOfSecondNode`, + }, + inlineArray: [1, 2, 3], + internal: { + type: `TestNode`, + contentDigest: `digest2`, + }, + }, + { + name: `test`, + } + ) + + describe(`Tracks nodes read from redux state cache`, () => { + it(`Tracks inline objects`, () => { + const node = getNode(`id1`) + const inlineObject = node.inlineObject + const trackedRootNode = findRootNode(inlineObject) + + expect(trackedRootNode).toEqual(node) + }) + + it(`Tracks inline arrays`, () => { + const node = getNode(`id1`) + const inlineObject = node.inlineArray + const trackedRootNode = findRootNode(inlineObject) + + expect(trackedRootNode).toEqual(node) + }) + + it(`Doesn't track copied objects`, () => { + const node = getNode(`id1`) + const copiedInlineObject = { ...node.inlineObject } + const trackedRootNode = findRootNode(copiedInlineObject) + + expect(trackedRootNode).not.toEqual(node) + }) + }) + + describe(`Tracks nodes created using createNode action`, () => { + it(`Tracks inline objects`, () => { + const node = getNode(`id2`) + const inlineObject = node.inlineObject + const trackedRootNode = findRootNode(inlineObject) + + expect(trackedRootNode).toEqual(node) + }) + }) + + describe(`Tracks nodes returned by running sift`, () => { + let type, nodes + + beforeAll(async () => { + type = (await buildNodeTypes()).testNode.nodeObjectType + nodes = getNodes() + }) + + it(`Tracks objects when running query without filter`, async () => { + const result = await runSift({ + args: {}, + nodes, + type, + connection: true, + }) + + expect(result.edges.length).toEqual(2) + expect(findRootNode(result.edges[0].node.inlineObject)).toEqual( + result.edges[0].node + ) + expect(findRootNode(result.edges[1].node.inlineObject)).toEqual( + result.edges[1].node + ) + }) + + it(`Tracks objects when running query with filter`, async () => { + const result = await runSift({ + args: { + filter: { + inlineObject: { + field: { + eq: `fieldOfSecondNode`, + }, + }, + }, + }, + nodes, + type, + connection: true, + }) + + expect(result.edges.length).toEqual(1) + expect(findRootNode(result.edges[0].node.inlineObject)).toEqual( + result.edges[0].node + ) + }) + }) +}) diff --git a/packages/gatsby/src/schema/node-tracking.js b/packages/gatsby/src/schema/node-tracking.js new file mode 100644 index 0000000000000..45e05b336a6d0 --- /dev/null +++ b/packages/gatsby/src/schema/node-tracking.js @@ -0,0 +1,81 @@ +const _ = require(`lodash`) +const { getNode, getNodes } = require(`../redux`) + +/** + * Map containing links between inline objects or arrays + * and Node that contains them + * @type {Object.<(Object|Array),string>} + */ +const rootNodeMap = new WeakMap() + +const getRootNodeId = node => rootNodeMap.get(node) + +/** + * Add link between passed data and Node. This function shouldn't be used + * directly. Use higher level `trackInlineObjectsInRootNode` + * @see trackInlineObjectsInRootNode + * @param {(Object|Array)} data Inline object or array + * @param {string} nodeId Id of node that contains data passed in first parameter + */ +const addRootNodeToInlineObject = (data, nodeId) => { + if (_.isPlainObject(data) || _.isArray(data)) { + _.each(data, o => addRootNodeToInlineObject(o, nodeId)) + rootNodeMap.set(data, nodeId) + } +} + +/** + * Adds link between inline objects/arrays contained in Node object + * and that Node object. + * @param {Node} node Root Node + */ +const trackInlineObjectsInRootNode = node => { + _.each(node, (v, k) => { + // Ignore the node internal object. + if (k === `internal`) { + return + } + addRootNodeToInlineObject(v, node.id) + }) + return node +} +exports.trackInlineObjectsInRootNode = trackInlineObjectsInRootNode + +/** + * Finds top most ancestor of node that contains passed Object or Array + * @param {(Object|Array)} obj Object/Array belonging to Node object or Node object + * @returns {Node} Top most ancestor + */ +function findRootNode(obj) { + // Find the root node. + let rootNode = obj + let whileCount = 0 + let rootNodeId + while ( + (rootNodeId = getRootNodeId(rootNode) || rootNode.parent) && + (getNode(rootNode.parent) !== undefined || getNode(rootNodeId)) && + whileCount < 101 + ) { + if (rootNodeId) { + rootNode = getNode(rootNodeId) + } else { + rootNode = getNode(rootNode.parent) + } + whileCount += 1 + if (whileCount > 100) { + console.log( + `It looks like you have a node that's set its parent as itself`, + rootNode + ) + } + } + + return rootNode +} + +exports.findRootNode = findRootNode + +// Track nodes that are already in store +_.each(getNodes(), node => { + trackInlineObjectsInRootNode(node) +}) diff --git a/packages/gatsby/src/schema/run-sift.js b/packages/gatsby/src/schema/run-sift.js index b6f498913cc9d..a0e1f7d042b6d 100644 --- a/packages/gatsby/src/schema/run-sift.js +++ b/packages/gatsby/src/schema/run-sift.js @@ -5,7 +5,7 @@ const { connectionFromArray } = require(`graphql-skip-limit`) const { createPageDependency } = require(`../redux/actions/add-page-dependency`) const prepareRegex = require(`./prepare-regex`) const Promise = require(`bluebird`) -const { trackInlineObjectsInRootNode } = require(`../redux`) +const { trackInlineObjectsInRootNode } = require(`./node-tracking`) function awaitSiftField(fields, node, k) { const field = fields[k] diff --git a/packages/gatsby/src/schema/types/type-file.js b/packages/gatsby/src/schema/types/type-file.js index 0f84c3e288f4f..7a869ab9f6b51 100644 --- a/packages/gatsby/src/schema/types/type-file.js +++ b/packages/gatsby/src/schema/types/type-file.js @@ -6,7 +6,8 @@ const isRelativeUrl = require(`is-relative-url`) const normalize = require(`normalize-path`) const systemPath = require(`path`) -const { getNode, getNodes, getRootNodeId } = require(`../../redux`) +const { getNodes } = require(`../../redux`) +const { findRootNode } = require(`../node-tracking`) const { createPageDependency, } = require(`../../redux/actions/add-page-dependency`) @@ -24,33 +25,6 @@ export function setFileNodeRootType(fileNodeRootType) { } } -function findRootNode(node) { - // Find the root node. - let rootNode = node - let whileCount = 0 - let rootNodeId - while ( - (rootNodeId = getRootNodeId(rootNode) || rootNode.parent) && - (getNode(rootNode.parent) !== undefined || getNode(rootNodeId)) && - whileCount < 101 - ) { - if (rootNodeId) { - rootNode = getNode(rootNodeId) - } else { - rootNode = getNode(rootNode.parent) - } - whileCount += 1 - if (whileCount > 100) { - console.log( - `It looks like you have a node that's set its parent as itself`, - rootNode - ) - } - } - - return rootNode -} - function pointsToFile(nodes, key, value) { const looksLikeFile = _.isString(value) &&