Skip to content
140 changes: 140 additions & 0 deletions packages/gatsby-dev-cli/src/__tests__/watch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
jest.mock(`chokidar`, () => {
return {
watch: jest.fn(),
}
})
jest.mock(`fs-extra`, () => {
return {
copy: jest.fn(),
existsSync: jest.fn(),
}
})
const chokidar = require(`chokidar`)
const fs = require(`fs-extra`)
const path = require(`path`)
const watch = require(`../watch`)

let on
beforeEach(() => {
fs.copy.mockReset()
fs.existsSync.mockImplementation(() => true)
chokidar.watch.mockImplementation(() => {
const mock = {
on: jest.fn().mockImplementation(() => mock),
}
on = mock.on
return mock
})
})

describe(`watching`, () => {
const callEventCallback = (...args) =>
on.mock.calls[0].slice(-1).pop()(...args)
const callReadyCallback = (...args) =>
on.mock.calls[1].slice(-1).pop()(...args)

const args = [process.cwd(), [`gatsby`], {}]

it(`watches files`, () => {
watch(...args)
expect(chokidar.watch).toHaveBeenCalledTimes(1)
expect(chokidar.watch).toHaveBeenCalledWith(expect.any(Array), {
ignored: [expect.any(Function)],
})
})

it(`registers on handlers`, () => {
watch(...args)

expect(on).toHaveBeenCalledTimes(2)
expect(on).toHaveBeenLastCalledWith(`ready`, expect.any(Function))
})

describe(`copying files`, () => {
it(`does not copy files on non-watch event`, () => {
watch(...args)

callEventCallback(`test`)

expect(fs.copy).not.toHaveBeenCalled()
})

it(`copies files on watch event`, () => {
const filePath = path.join(process.cwd(), `packages/gatsby/dist/index.js`)
watch(...args)
callEventCallback(`add`, filePath)

expect(fs.copy).toHaveBeenCalledTimes(1)
expect(fs.copy).toHaveBeenCalledWith(
filePath,
`node_modules/gatsby/dist/index.js`,
expect.any(Function)
)
})

it(`copies cache-dir files`, () => {
watch(...args)

const filePath = path.join(
process.cwd(),
`packages/gatsby/cache-dir/register-service-worker.js`
)
callEventCallback(`add`, filePath)

expect(fs.copy).toHaveBeenCalledTimes(2)
expect(fs.copy).toHaveBeenLastCalledWith(
filePath,
`.cache/register-service-worker.js`,
expect.any(Function)
)
})

it(`filters non-existant files/directories`, () => {
fs.existsSync.mockReset().mockImplementation(file => false)

watch(...args)

expect(chokidar.watch).toHaveBeenCalledWith([], expect.any(Object))
})

it(`filters duplicate directories`, () => {
watch(process.cwd(), [`gatsby`, `gatsby`], {})

expect(chokidar.watch).toHaveBeenCalledWith(
[expect.stringContaining(`gatsby`)],
expect.any(Object)
)
})
})

describe(`exiting`, () => {
let realProcess
beforeAll(() => {
realProcess = global.process

global.process = {
...realProcess,
exit: jest.fn(),
}
})

afterAll(() => {
global.process = realProcess
})

it(`does not exit if scanOnce is not defined`, () => {
watch(...args)
callReadyCallback()

expect(process.exit).not.toHaveBeenCalled()
})

it(`exits if scanOnce is defined`, async () => {
watch(process.cwd(), [`gatsby`], { scanOnce: true })

await callReadyCallback()

expect(process.exit).toHaveBeenCalledTimes(1)
})
})
})
35 changes: 22 additions & 13 deletions packages/gatsby-dev-cli/src/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,33 @@ const copyPath = (oldPath, newPath, quiet) =>
})
})

/*
* non-existant packages break on('ready')
* See: https://github.com/paulmillr/chokidar/issues/449
*/
function watch(root, packages, { scanOnce, quiet }) {
const ignored = [
/[/\\]node_modules[/\\]/i,
/\.git/i,
/\.DS_Store/,
].concat(
const ignored = [/[/\\]node_modules[/\\]/i, /\.git/i, /\.DS_Store/].concat(
packages.map(p => new RegExp(`${p}[\\/\\\\]src[\\/\\\\]`, `i`))
)
const watchers = packages.map(p => path.join(root, `/packages/`, p))
const watchers = _.uniq(
packages
.map(p => path.join(root, `/packages/`, p))
.filter(p => fs.existsSync(p))
)

let allCopies = []

chokidar.watch(watchers, {
ignored: [filePath => _.some(ignored, reg => reg.test(filePath))],
})
chokidar
.watch(watchers, {
ignored: [filePath => _.some(ignored, reg => reg.test(filePath))],
})
.on(`all`, (event, filePath) => {
if (event === `change` || event === `add`) {
const packageName = path.basename(path.dirname(filePath.split(`packages/`).pop()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given something like packages/gatsby/dist/gatsby/cache-dir/something.js this was inferring the packageName to be cache-dir rather than gatsby. This is the main fix here.

const watchEvents = [`change`, `add`]
if (_.includes(watchEvents, event)) {
const [packageName] = filePath
.split(`packages/`)
.pop()
.split(`/`)
const prefix = path.join(root, `/packages/`, packageName)

// Copy it over local version.
Expand Down Expand Up @@ -71,14 +80,14 @@ function watch(root, packages, { scanOnce, quiet }) {
allCopies = allCopies.concat(localCopies)
}
})
.on(`ready`, () => {
.on(`ready`, () =>
// all files watched, quit once all files are copied if necessary
Promise.all(allCopies).then(() => {
if (scanOnce) {
quit()
}
})
})
)
}

module.exports = watch
2 changes: 1 addition & 1 deletion scripts/publish-site.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ cd "$1" || exit
yarn

echo "=== Copying built Gatsby to website."
gatsby-dev --scan-once --quiet
gatsby-dev --scan-once

# copy file if target dir exists
FRAGMENTSDIR="node_modules/gatsby-transformer-sharp/src"
Expand Down