Skip to content

Commit

Permalink
node tracking - add tests (#4045)
Browse files Browse the repository at this point in the history
* move node tracking code to separate dedicated file

* Add jsdoc for findRootNode

* add tests for node tracking
  • Loading branch information
pieh authored and KyleAMathews committed Feb 15, 2018
1 parent 243a726 commit 0a6fb37
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 80 deletions.
7 changes: 2 additions & 5 deletions packages/gatsby/src/redux/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
46 changes: 0 additions & 46 deletions packages/gatsby/src/redux/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down Expand Up @@ -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`)
138 changes: 138 additions & 0 deletions packages/gatsby/src/schema/__tests__/node-tracking-test.js
Original file line number Diff line number Diff line change
@@ -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
)
})
})
})
81 changes: 81 additions & 0 deletions packages/gatsby/src/schema/node-tracking.js
Original file line number Diff line number Diff line change
@@ -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)
})
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/run-sift.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
30 changes: 2 additions & 28 deletions packages/gatsby/src/schema/types/type-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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) &&
Expand Down

0 comments on commit 0a6fb37

Please sign in to comment.