From 74cadb2a6153a662da670f1e1118705bae0acfef Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Tue, 29 Aug 2017 12:34:44 -0400 Subject: [PATCH] test: Mock `chokidar` as CI filesystem seems unreliable --- src/index.js | 2 +- test/helpers/karma.js | 17 +++--- test/helpers/mock.js | 68 ++++++++++++++++------ test/helpers/utils.js | 18 +----- test/integration.test.js | 68 ++++++---------------- test/unit.test.js | 122 +++++++++++++++++++++++---------------- 6 files changed, 151 insertions(+), 144 deletions(-) diff --git a/src/index.js b/src/index.js index 47ebeaf..d87dbc4 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ import sass from 'node-sass'; * Sass preprocessor factory. * * @param {Object} args Config object of custom preprocessor. - * @param {Object} [config={}] KArma's config. + * @param {Object} [config={}] Karma's config. * @param {Object} logger Karma's logger. * @param {Object} server Karma's server. * @return {Function} the function to preprocess files. diff --git a/test/helpers/karma.js b/test/helpers/karma.js index d7504ec..1b88dca 100644 --- a/test/helpers/karma.js +++ b/test/helpers/karma.js @@ -1,6 +1,7 @@ import pEvent from 'p-event'; import {Server, constants} from 'karma'; import karmaPreprocessor from '../../lib/index'; +import {mockFactory} from './mock'; /** * Base Karma configuration tu run preprocessor. @@ -20,7 +21,6 @@ const KARMA_CONFIG = { colors: true, logLevel: constants.LOG_DISABLE, browsers: ['PhantomJS'], - plugins: ['@metahub/karma-jasmine-jquery', 'karma-*', karmaPreprocessor], }; /** @@ -45,7 +45,7 @@ const KARMA_CONFIG = { * @return {Promise} A `Promise` that resolve to the Karma execution results. */ export async function run(files, config) { - const server = createServer(files, config, false); + const server = createServer(files, config, false, karmaPreprocessor); server.start(); const result = await waitForRunComplete(server); @@ -64,11 +64,12 @@ export async function run(files, config) { * @param {Object} [config] configuration to pass to the preprocessor. * @return {Server} The started Karma Server. */ -export function watch(files, config) { - const server = createServer(files, config, true); +export async function watch(files, config) { + const {factory, watcher} = mockFactory(true); + const server = createServer(files, config, true, factory); server.start(); - return server; + return {server, watcher: await watcher}; } /** @@ -78,9 +79,10 @@ export function watch(files, config) { * @param {Array} files path of the scss/sass files and unit tests. * @param {Object} [config] configuration to pass to the preprocessor. * @param {boolean} autoWatch `true` for autoWatch mode, `false` for a single run. + * @param {Object} processorFactory Karma plugin factory. * @return {Server} the configured Karma Server. */ -function createServer(files, config, autoWatch) { +function createServer(files, config, autoWatch, processorFactory) { return new Server( Object.assign(KARMA_CONFIG, { files: Array.isArray(files) ? files : [files], @@ -88,6 +90,7 @@ function createServer(files, config, autoWatch) { customPreprocessors: {custom_sass: Object.assign({base: 'sass'}, config)}, singleRun: !autoWatch, autoWatch, + plugins: ['@metahub/karma-jasmine-jquery', 'karma-*', processorFactory], }), () => 0 ); @@ -104,7 +107,7 @@ export async function waitForRunComplete(server) { try { const [, result] = await pEvent(server, 'run_complete', { multiArgs: true, - timeout: 10000, + timeout: 50000, rejectionEvents: ['browser_error'], }); diff --git a/test/helpers/mock.js b/test/helpers/mock.js index 2471802..d1d8ccd 100644 --- a/test/helpers/mock.js +++ b/test/helpers/mock.js @@ -1,45 +1,77 @@ +import {EventEmitter} from 'events'; import proxyquire from 'proxyquire'; import {spy, stub} from 'sinon'; import pify from 'pify'; -import chokidar from 'chokidar'; /** - * @typedef {Object} Mock + * @typedef {Object} MockPreprocessor * @property {Function} preprocessor The preprocessor function. * @property {Spy} debug A spied debug log function. * @property {Spy} error A spied error log function. * @property {Spy} info A spied info log function. * @property {Spy} refreshFiles A spied server's refreshFiles function. - * @property {Spy} add A spied watcher's add function. - * @property {Spy} unwatch A spied watcher's unwatch function. - * @property {FSWatcher} watcher The preprocessor local watcher. + * @property {EventEmitter} watcher The preprocessor local watcher. + * @property {Spy} watcher.add A spied watcher's add function. + * @property {Spy} watcher.unwatch A spied watcher's unwatch function. * @property {Stub} FSWatcher A stubbed local watcher constructor. */ +/** + * @typedef {Object} MockFactory + * @property {Oject} factory The preprocessor factory. + * @property {Promise} watcher a Promise that resolves to preprocessor local watcher. + * @property {Stub} FSWatcher A stubbed local watcher constructor. + */ + +/** + * Create a mocked preprocessor factory. + * + * @method mockFactory + * @param {Boolean} autoWatch `true` for autoWatch mode, `false` for a single run. + * @return {MockFactory} mocked preprocessor factory and watcher. + */ +export function mockFactory(autoWatch) { + const FSWatcher = stub(); + + return { + factory: proxyquire('../../lib/index', {chokidar: {FSWatcher}}), + watcher: pify(callback => { + if (autoWatch) { + return FSWatcher.callsFake(() => { + const emitter = new EventEmitter(); + const add = spy(); + const unwatch = spy(); + + emitter.add = add; + emitter.unwatch = unwatch; + callback(null, emitter); + return emitter; + }); + } else { + FSWatcher.returns(new EventEmitter()); + return callback(null); + } + })(), + FSWatcher, + }; +} + /** * Create a mocked preprocessor. * * @method mockPreprocessor * @param {Object} [args={}] custom preprocessor config to pass to the factory. * @param {Object} [config={}] Karma config to pass to the factory. - * @return {Object} mocked preprocessor function and spies. + * @return {MockPreprocessor} mocked preprocessor function and spies. */ -export default function mockPreprocessor(args = {}, config = {}) { - let add; - let unwatch; - let watcher; - const FSWatcher = stub().callsFake((...watcherArgs) => { - watcher = new chokidar.FSWatcher(...watcherArgs); - add = spy(watcher, 'add'); - unwatch = spy(watcher, 'unwatch'); - return watcher; - }); +export async function mockPreprocessor(args = {}, config = {}) { const debug = spy(); const error = spy(); const info = spy(); const refreshFiles = spy(); + const {factory, watcher, FSWatcher} = mockFactory(config.autoWatch); const preprocessor = pify( - proxyquire('../../lib/index', {chokidar: {FSWatcher}})['preprocessor:sass'][1]( + factory['preprocessor:sass'][1]( args, config, { @@ -51,5 +83,5 @@ export default function mockPreprocessor(args = {}, config = {}) { ) ); - return {preprocessor, debug, error, info, refreshFiles, add, unwatch, watcher, FSWatcher}; + return {preprocessor, debug, error, info, refreshFiles, watcher: await watcher, FSWatcher}; } diff --git a/test/helpers/utils.js b/test/helpers/utils.js index 1bf9cdb..45c185c 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -14,20 +14,6 @@ export function tmp(filename) { return path.join('test/fixtures/.tmp', uuid(), filename || ''); } -/** - * Return a Promise that resolve after a delay. - * - * @method sleep - * @param {Number} delay time after which to resolve the Promise. - * @return {Promise} a Promise that resolve after a delay. - */ -export function sleep(delay) { - // eslint-disable-next-line promise/avoid-new - return new Promise(resolve => { - setTimeout(resolve, delay); - }); -} - /* eslint-disable no-magic-numbers */ /** * Return a Promise that resolve when an event is emitted and reject after a timeout expire if the event is not emitted. @@ -35,10 +21,10 @@ export function sleep(delay) { * @method waitFor * @param {Object} emitter object that emit events. * @param {string} event event to listen to. - * @param {Number} [timeout=5000] maximum time to wait for the event to be emitted. + * @param {Number} [timeout=30000] maximum time to wait for the event to be emitted. * @return {Promise} Promise tht resolve when the event is emitted. */ -export function waitFor(emitter, event, timeout = 5000) { +export function waitFor(emitter, event, timeout = 30000) { return pEvent(emitter, event, {timeout}); } diff --git a/test/integration.test.js b/test/integration.test.js index f7a2055..43c57e0 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1,7 +1,6 @@ import path from 'path'; -import {utimes, copy, readFile, outputFile} from 'fs-extra'; +import {copy} from 'fs-extra'; import test from 'ava'; -import pTimeout from 'p-timeout'; import {stub} from 'sinon'; import {run, watch, waitForRunComplete} from './helpers/karma'; import {tmp} from './helpers/utils'; @@ -17,17 +16,23 @@ test.after(() => { }); test('Compile scss file', async t => { - const {success, error, disconnected} = await run(['test/fixtures/basic.scss', 'test/fixtures/styles.test.js']); + const {success, error, disconnected, errMsg} = await run([ + 'test/fixtures/basic.scss', + 'test/fixtures/styles.test.js', + ]); - t.ifError(error, 'Karma returned an error'); + t.ifError(error, `Karma returned the error: ${errMsg}`); t.ifError(disconnected, 'Karma disconnected'); t.is(success, 1, 'Expected 1 test successful'); }); test('Compile scss file with custom preprocessor', async t => { - const {success, error, disconnected} = await run(['test/fixtures/basic.custom.scss', 'test/fixtures/styles.test.js']); + const {success, error, disconnected, errMsg} = await run([ + 'test/fixtures/basic.custom.scss', + 'test/fixtures/styles.test.js', + ]); - t.ifError(error, 'Karma returned an error'); + t.ifError(error, `Karma returned the error: ${errMsg}`); t.ifError(disconnected, 'Karma disconnected'); t.is(success, 1, 'Expected 1 test successful'); }); @@ -52,64 +57,25 @@ test('Re-compile scss file when dependency is modified', async t => { copy('test/fixtures/partials/_sub-partial.scss', subPartial), copy('test/fixtures/with-partial.scss', fixture), ]); - const server = await watch( + const {server, watcher} = await watch( [fixture.replace('fixtures', '*').replace('with', '+(with|nomatch)'), 'test/fixtures/styles.test.js'], {options: {includePaths: [includePath]}} ); try { - let {success, error, disconnected} = await waitForRunComplete(server); + let {success, error, disconnected, errMsg} = await waitForRunComplete(server); - t.ifError(error, 'Karma returned an error'); + t.ifError(error, `Karma returned the error: ${errMsg}`); t.ifError(disconnected, 'Karma disconnected'); t.is(success, 1, 'Expected 1 test successful'); - utimes(partial, Date.now() / 1000, Date.now() / 1000); - ({success, error, disconnected} = await waitForRunComplete(server)); + watcher.emit('change', partial); + ({success, error, disconnected, errMsg} = await waitForRunComplete(server)); - t.ifError(error, 'Karma returned an error'); + t.ifError(error, `Karma returned the error: ${errMsg}`); t.ifError(disconnected, 'Karma disconnected'); t.is(success, 1, 'Expected 1 test successful'); } finally { await server.emitAsync('exit'); } }); - -test('Do not recompile scss file when dependency is not imported anymore', async t => { - const dir = path.resolve(tmp()); - const fixture = path.join(dir, 'with-partial.scss'); - const includePath = path.join(dir, 'partials'); - const partial = path.join(includePath, '_partial.scss'); - const partialAlt = path.join(includePath, '_partial-alt.scss'); - const subPartial = path.join(includePath, '_sub-partial.scss'); - - await Promise.all([ - copy('test/fixtures/partials/_partial.scss', partial), - copy('test/fixtures/partials/_partial.scss', partialAlt), - copy('test/fixtures/partials/_sub-partial.scss', subPartial), - copy('test/fixtures/with-partial.scss', fixture), - ]); - const server = await watch([fixture, 'test/fixtures/styles.test.js'], {options: {includePaths: [includePath]}}); - - try { - let {success, error, disconnected} = await waitForRunComplete(server); - - t.ifError(error, 'Karma returned an error'); - t.ifError(disconnected, 'Karma disconnected'); - t.is(success, 1, 'Expected 1 test successful'); - - await outputFile( - fixture, - (await readFile(fixture)).toString().replace(`@import 'partial';`, `@import 'partial-alt';`) - ); - ({success, error, disconnected} = await waitForRunComplete(server)); - t.ifError(error, 'Karma returned an error'); - t.ifError(disconnected, 'Karma disconnected'); - t.is(success, 1, 'Expected 1 test successful'); - - utimes(partial, Date.now() / 1000, Date.now() / 1000); - await t.throws(waitForRunComplete(server), pTimeout.TimeoutError); - } finally { - await server.emitAsync('exit'); - } -}); diff --git a/test/unit.test.js b/test/unit.test.js index 2bb9b2b..8b2e84d 100644 --- a/test/unit.test.js +++ b/test/unit.test.js @@ -1,13 +1,13 @@ import path from 'path'; -import {readFile, utimes, copy, outputFile, remove} from 'fs-extra'; +import {readFile, copy, outputFile, remove} from 'fs-extra'; import test from 'ava'; import {spy, match} from 'sinon'; -import {tmp, sleep, waitFor, compile} from './helpers/utils'; -import mockPreprocessor from './helpers/mock'; +import {tmp, waitFor, compile} from './helpers/utils'; +import {mockPreprocessor} from './helpers/mock'; test('Compile scss file', async t => { const fixture = 'test/fixtures/basic.scss'; - const {preprocessor, debug} = mockPreprocessor(); + const {preprocessor, debug} = await mockPreprocessor(); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -17,7 +17,7 @@ test('Compile scss file', async t => { test('Compile sass file', async t => { const fixture = 'test/fixtures/basic.sass'; - const {preprocessor, debug} = mockPreprocessor(); + const {preprocessor, debug} = await mockPreprocessor(); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -28,7 +28,7 @@ test('Compile sass file', async t => { test('Compile scss file with sourcemap (options.sourceMap)', async t => { const fixture = 'test/fixtures/basic.scss'; const options = {sourceMap: true}; - const {preprocessor, debug} = mockPreprocessor({}, {sassPreprocessor: {options}}); + const {preprocessor, debug} = await mockPreprocessor({}, {sassPreprocessor: {options}}); const file = {originalPath: fixture}; const {css, map} = await compile(fixture, options); @@ -43,7 +43,7 @@ test('Compile scss file with sourcemap (options.sourceMap)', async t => { test('Compile scss file with sourcemap (options.map)', async t => { const fixture = 'test/fixtures/basic.scss'; const options = {map: true}; - const {preprocessor, debug} = mockPreprocessor({}, {sassPreprocessor: {options}}); + const {preprocessor, debug} = await mockPreprocessor({}, {sassPreprocessor: {options}}); const file = {originalPath: fixture}; const {css, map} = await compile(fixture, options); @@ -58,7 +58,7 @@ test('Compile scss file with sourcemap (options.map)', async t => { test('Compile scss file with sourcemap (options.sourceMap) and custom preprocessor', async t => { const fixture = 'test/fixtures/basic.custom.scss'; const options = {sourceMap: true}; - const {preprocessor, debug} = mockPreprocessor({options}); + const {preprocessor, debug} = await mockPreprocessor({options}); const file = {originalPath: fixture}; const {css, map} = await compile(fixture, options); @@ -73,7 +73,7 @@ test('Compile scss file with sourcemap (options.sourceMap) and custom preprocess test('Compile scss file with sourcemap (options.map) and custom preprocessor', async t => { const fixture = 'test/fixtures/basic.custom.scss'; const options = {map: true}; - const {preprocessor, debug} = mockPreprocessor({options}); + const {preprocessor, debug} = await mockPreprocessor({options}); const file = {originalPath: fixture}; const {css, map} = await compile(fixture, options); @@ -88,7 +88,7 @@ test('Compile scss file with sourcemap (options.map) and custom preprocessor', a test('Compile scss file with partial import', async t => { const fixture = 'test/fixtures/with-partial.scss'; const options = {includePaths: ['test/fixtures/partials']}; - const {preprocessor, debug} = mockPreprocessor({}, {sassPreprocessor: {options}}); + const {preprocessor, debug} = await mockPreprocessor({}, {sassPreprocessor: {options}}); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture, options)).css); @@ -99,7 +99,7 @@ test('Compile scss file with partial import', async t => { test('Compile scss file with options', async t => { const fixture = 'test/fixtures/basic.scss'; const options = {precision: 8, sourceComments: true, outputStyle: 'compressed'}; - const {preprocessor, debug} = mockPreprocessor({}, {sassPreprocessor: {options}}); + const {preprocessor, debug} = await mockPreprocessor({}, {sassPreprocessor: {options}}); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture, options)).css); @@ -109,7 +109,7 @@ test('Compile scss file with options', async t => { test('Compile scss file with non css extension', async t => { const fixture = 'test/fixtures/basic.txt'; - const {preprocessor, debug} = mockPreprocessor(); + const {preprocessor, debug} = await mockPreprocessor(); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -119,7 +119,7 @@ test('Compile scss file with non css extension', async t => { test('Compile scss file with no extension', async t => { const fixture = 'test/fixtures/basic'; - const {preprocessor, debug} = mockPreprocessor(); + const {preprocessor, debug} = await mockPreprocessor(); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -130,7 +130,7 @@ test('Compile scss file with no extension', async t => { test('Compile scss file with custom transformPath', async t => { const fixture = 'test/fixtures/basic.txt'; const transformPath = spy(filePath => filePath.replace(/\.(txt)$/, '.css').replace('fixtures/', '')); - const {preprocessor, debug} = mockPreprocessor({}, {sassPreprocessor: {transformPath}}); + const {preprocessor, debug} = await mockPreprocessor({}, {sassPreprocessor: {transformPath}}); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -142,7 +142,7 @@ test('Compile scss file with custom transformPath', async t => { test('Compile scss file with custom transformPath and custom preprocessor', async t => { const fixture = 'test/fixtures/basic.custom.txt'; const transformPath = spy(filePath => filePath.replace(/\.(txt)$/, '.css').replace('fixtures/', '')); - const {preprocessor, debug} = mockPreprocessor({transformPath}); + const {preprocessor, debug} = await mockPreprocessor({transformPath}); const file = {originalPath: fixture}; t.is((await preprocessor(await readFile(fixture), file)).toString(), (await compile(fixture)).css); @@ -153,7 +153,7 @@ test('Compile scss file with custom transformPath and custom preprocessor', asyn test('Log error on invalid scss file', async t => { const fixture = 'test/fixtures/error.scss'; - const {preprocessor, debug, error} = mockPreprocessor(); + const {preprocessor, debug, error} = await mockPreprocessor(); const file = {originalPath: fixture}; const err = await t.throws(preprocessor(await readFile(fixture), file), Error); @@ -166,11 +166,11 @@ test('Log error on invalid scss file', async t => { t.true(error.firstCall.calledWith(match.string, match('no mixin named text-red'), fixture, match(10))); }); -test('Instanciate watcher only if autoWatch is true', t => { - let {FSWatcher} = mockPreprocessor(); +test('Instanciate watcher only if autoWatch is true', async t => { + let {FSWatcher} = await mockPreprocessor(); t.true(FSWatcher.notCalled); - ({FSWatcher} = mockPreprocessor({}, {autoWatch: true})); + ({FSWatcher} = await mockPreprocessor({}, {autoWatch: true})); t.true(FSWatcher.calledOnce); }); @@ -180,7 +180,7 @@ test('Add dependency to watcher', async t => { const partial = path.resolve('test/fixtures/partials/_partial.scss'); const subPartial = path.resolve('test/fixtures/partials/_sub-partial.scss'); const options = {includePaths: ['test/fixtures/partials']}; - const {preprocessor, add, debug} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, {files: [{pattern: fixture, watched: true}], autoWatch: true, sassPreprocessor: {options}} ); @@ -189,8 +189,8 @@ test('Add dependency to watcher', async t => { await preprocessor(await readFile(fixture), file); t.true(debug.secondCall.calledWith(match('Watching'), subPartial)); t.true(debug.thirdCall.calledWith(match('Watching'), partial)); - t.true(add.firstCall.calledWith(match.array.deepEquals([subPartial, partial]))); - t.true(add.calledOnce); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([subPartial, partial]))); + t.true(watcher.add.calledOnce); }); test('Add dependency to watcher for file added with glob', async t => { @@ -199,7 +199,7 @@ test('Add dependency to watcher for file added with glob', async t => { const partial = path.resolve('test/fixtures/partials/_partial.scss'); const subPartial = path.resolve('test/fixtures/partials/_sub-partial.scss'); const options = {includePaths: ['test/fixtures/partials']}; - const {preprocessor, add, debug} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, {files: [{pattern: glob, watched: true}], autoWatch: true, sassPreprocessor: {options}} ); @@ -208,21 +208,21 @@ test('Add dependency to watcher for file added with glob', async t => { await preprocessor(await readFile(fixture), file); t.true(debug.secondCall.calledWith(match('Watching'), subPartial)); t.true(debug.thirdCall.calledWith(match('Watching'), partial)); - t.true(add.firstCall.calledWith(match.array.deepEquals([subPartial, partial]))); - t.true(add.calledOnce); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([subPartial, partial]))); + t.true(watcher.add.calledOnce); }); test('Do not add dependency to watcher if parent is not watched', async t => { const fixture = 'test/fixtures/with-partial.scss'; const options = {includePaths: ['test/fixtures/partials']}; - const {preprocessor, add} = mockPreprocessor( + const {preprocessor, watcher} = await mockPreprocessor( {}, {autoWatch: true, files: [{pattern: fixture, watched: false}], sassPreprocessor: {options}} ); const file = {originalPath: fixture}; await preprocessor(await readFile(fixture), file); - t.true(add.notCalled); + t.true(watcher.add.notCalled); }); test('Add dependency to watcher only once, even when its referenced multiple times', async t => { @@ -234,7 +234,7 @@ test('Add dependency to watcher only once, even when its referenced multiple tim const partialAlt = path.join(includePath, '_partial-alt.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, add, debug} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, { autoWatch: true, @@ -255,13 +255,35 @@ test('Add dependency to watcher only once, even when its referenced multiple tim await preprocessor(await readFile(fixture), file); t.true(debug.secondCall.calledWith(match('Watching'), partial)); t.true(debug.thirdCall.calledWith(match('Watching'), subPartial)); - t.true(add.firstCall.calledWith(match.array.deepEquals([partial, subPartial]))); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([partial, subPartial]))); debug.reset(); await preprocessor(await readFile(otherFixture), otherFile); - t.true(add.calledOnce); + t.true(watcher.add.calledOnce); t.true(debug.calledOnce); }); +test('Add dependency to watcher only once if file is overwritten', async t => { + const fixture = 'test/fixtures/with-partial.scss'; + const partial = path.resolve('test/fixtures/partials/_partial.scss'); + const subPartial = path.resolve('test/fixtures/partials/_sub-partial.scss'); + const options = {includePaths: ['test/fixtures/partials']}; + const {preprocessor, watcher, debug, refreshFiles} = await mockPreprocessor( + {}, + {files: [{pattern: fixture, watched: true}], autoWatch: true, sassPreprocessor: {options}} + ); + const file = {originalPath: fixture}; + + await preprocessor(await readFile(fixture), file); + t.true(debug.secondCall.calledWith(match('Watching'), subPartial)); + t.true(debug.thirdCall.calledWith(match('Watching'), partial)); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([subPartial, partial]))); + t.true(watcher.add.calledOnce); + debug.reset(); + watcher.emit('add', subPartial); + await preprocessor(await readFile(fixture), file); + t.true(refreshFiles.notCalled); +}); + test('Remove dependency from watcher if not referenced anymore', async t => { const dir = path.resolve(tmp()); const fixture = path.join(dir, 'with-partial.scss'); @@ -270,7 +292,7 @@ test('Remove dependency from watcher if not referenced anymore', async t => { const partialAlt = path.join(includePath, '_partial-alt.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, add, debug, unwatch} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, {autoWatch: true, files: [{pattern: fixture, watched: true}], sassPreprocessor: {options}} ); @@ -283,19 +305,19 @@ test('Remove dependency from watcher if not referenced anymore', async t => { copy('test/fixtures/with-partial.scss', fixture), ]); await preprocessor(await readFile(fixture), file); - add.reset(); + watcher.add.reset(); debug.reset(); await outputFile( fixture, (await readFile(fixture)).toString().replace(`@import 'partial';`, `@import 'partial-alt';`) ); await preprocessor(await readFile(fixture), file); - t.true(unwatch.firstCall.calledWith(match.array.deepEquals([partial]))); + t.true(watcher.unwatch.firstCall.calledWith(match.array.deepEquals([partial]))); t.true(debug.thirdCall.calledWith(match('Stop watching'), partial)); - t.true(add.firstCall.calledWith(match.array.deepEquals([partialAlt]))); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([partialAlt]))); t.true(debug.secondCall.calledWith(match('Watching'), partialAlt)); - t.true(unwatch.calledOnce); - t.true(add.calledOnce); + t.true(watcher.unwatch.calledOnce); + t.true(watcher.add.calledOnce); }); test('Do not remove dependency from watcher when unreferenced, if another file still depends on it', async t => { @@ -307,7 +329,7 @@ test('Do not remove dependency from watcher when unreferenced, if another file s const partialAlt = path.join(includePath, '_partial-alt.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, add, debug, unwatch} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, { autoWatch: true, @@ -327,15 +349,15 @@ test('Do not remove dependency from watcher when unreferenced, if another file s ]); await preprocessor(await readFile(fixture), file); await preprocessor(await readFile(otherFixture), otherFile); - add.reset(); + watcher.add.reset(); debug.reset(); await outputFile( fixture, (await readFile(fixture)).toString().replace(`@import 'partial';`, `@import 'partial-alt';`) ); await preprocessor(await readFile(fixture), file); - t.true(add.firstCall.calledWith(match.array.deepEquals([path.resolve(partialAlt)]))); - t.true(unwatch.notCalled); + t.true(watcher.add.firstCall.calledWith(match.array.deepEquals([path.resolve(partialAlt)]))); + t.true(watcher.unwatch.notCalled); t.true(debug.calledTwice); }); @@ -348,7 +370,7 @@ test('Do not remove dependency from watcher when different files have differents const partialAlt = path.join(includePath, '_partial-alt.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, add, debug, unwatch} = mockPreprocessor( + const {preprocessor, watcher, debug} = await mockPreprocessor( {}, { autoWatch: true, @@ -371,11 +393,11 @@ test('Do not remove dependency from watcher when different files have differents (await readFile(fixture)).toString().replace(`@import 'partial';`, `@import 'partial-alt';`) ); await preprocessor(await readFile(fixture), file); - add.reset(); + watcher.add.reset(); debug.reset(); await preprocessor(await readFile(otherFixture), otherFile); - t.true(add.calledOnce); - t.true(unwatch.notCalled); + t.true(watcher.add.calledOnce); + t.true(watcher.unwatch.notCalled); t.true(debug.calledTwice); }); @@ -386,7 +408,7 @@ test('Call refreshFiles when dependency is modified', async t => { const partial = path.join(includePath, '_partial.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, watcher, info, refreshFiles} = mockPreprocessor( + const {preprocessor, watcher, info, refreshFiles} = await mockPreprocessor( {}, {autoWatch: true, files: [{pattern: fixture, watched: true}], sassPreprocessor: {options}} ); @@ -400,9 +422,7 @@ test('Call refreshFiles when dependency is modified', async t => { await preprocessor(await readFile(fixture), file); const change = waitFor(watcher, 'change'); - // eslint-disable-next-line no-magic-numbers - await sleep(150); - utimes(partial, Date.now() / 1000, Date.now() / 1000); + watcher.emit('change', partial); t.is(path.resolve(partial), await change); t.true(info.firstCall.calledWith(match('Changed file'), path.resolve(partial))); t.true(info.calledOnce); @@ -416,7 +436,7 @@ test('Call refreshFiles when dependency is deleted and added', async t => { const partial = path.join(includePath, '_partial.scss'); const subPartial = path.join(includePath, '_sub-partial.scss'); const options = {includePaths: [includePath]}; - const {preprocessor, watcher, info, refreshFiles} = mockPreprocessor( + const {preprocessor, watcher, info, refreshFiles} = await mockPreprocessor( {}, {autoWatch: true, files: [{pattern: fixture, watched: true}], sassPreprocessor: {options}} ); @@ -430,8 +450,7 @@ test('Call refreshFiles when dependency is deleted and added', async t => { await preprocessor(await readFile(fixture), file); const del = waitFor(watcher, 'unlink'); - // eslint-disable-next-line no-magic-numbers - await sleep(150); + watcher.emit('unlink', partial); remove(partial); t.is(path.resolve(partial), await del); t.true(info.firstCall.calledWith(match('Deleted file'), path.resolve(partial))); @@ -443,6 +462,7 @@ test('Call refreshFiles when dependency is deleted and added', async t => { const cpy = waitFor(watcher, 'add'); await copy('test/fixtures/partials/_partial.scss', partial); + watcher.emit('add', partial); t.is(path.resolve(partial), await cpy); t.true(info.firstCall.calledWith(match('Added file'), path.resolve(partial))); t.true(info.calledOnce);