Skip to content

Commit 98290c0

Browse files
committed
test: Mock chokidar as CI filesystem seems unreliable
1 parent e344b2a commit 98290c0

6 files changed

Lines changed: 153 additions & 150 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"karma-phantomjs-launcher": "^1.0.4",
5252
"nyc": "^11.0.3",
5353
"p-event": "^1.3.0",
54-
"p-timeout": "^1.2.0",
5554
"pify": "^3.0.0",
5655
"postcss": "^6.0.8",
5756
"postcss-import": "^10.0.0",

test/helpers/karma.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pEvent from 'p-event';
22
import {Server, constants} from 'karma';
33
import karmaPreprocessor from '../../lib/index';
4+
import {mockFactory} from './mock';
45

56
/**
67
* Base Karma configuration tu run preprocessor.
@@ -20,7 +21,6 @@ const KARMA_CONFIG = {
2021
colors: true,
2122
logLevel: constants.LOG_DISABLE,
2223
browsers: ['PhantomJS'],
23-
plugins: ['@metahub/karma-jasmine-jquery', 'karma-*', karmaPreprocessor],
2424
};
2525

2626
/**
@@ -45,7 +45,7 @@ const KARMA_CONFIG = {
4545
* @return {Promise<KarmaOutput>} A `Promise` that resolve to the Karma execution results.
4646
*/
4747
export async function run(files, config) {
48-
const server = createServer(files, config, false);
48+
const server = createServer(files, config, false, karmaPreprocessor);
4949

5050
server.start();
5151
const result = await waitForRunComplete(server);
@@ -64,11 +64,12 @@ export async function run(files, config) {
6464
* @param {Object} [config] configuration to pass to the preprocessor.
6565
* @return {Server} The started Karma Server.
6666
*/
67-
export function watch(files, config) {
68-
const server = createServer(files, config, true);
67+
export async function watch(files, config) {
68+
const {factory, watcher} = mockFactory(true);
69+
const server = createServer(files, config, true, factory);
6970

7071
server.start();
71-
return server;
72+
return {server, watcher: await watcher};
7273
}
7374

7475
/**
@@ -78,16 +79,18 @@ export function watch(files, config) {
7879
* @param {Array<string>} files path of the css files and unit tests.
7980
* @param {Object} [config] configuration to pass to the preprocessor.
8081
* @param {boolean} autoWatch `true` for autoWatch mode, `false` for a single run.
82+
* @param {Object} processorFactory Karma plugin factory.
8183
* @return {Server} the configured Karma Server.
8284
*/
83-
function createServer(files, config, autoWatch) {
85+
function createServer(files, config, autoWatch, processorFactory) {
8486
return new Server(
8587
Object.assign(KARMA_CONFIG, {
8688
files: Array.isArray(files) ? files : [files],
8789
postcssPreprocessor: config,
8890
customPreprocessors: {custom_postcss: Object.assign({base: 'postcss'}, config)},
8991
singleRun: !autoWatch,
9092
autoWatch,
93+
plugins: ['@metahub/karma-jasmine-jquery', 'karma-*', processorFactory],
9194
}),
9295
() => 0
9396
);
@@ -104,7 +107,7 @@ export async function waitForRunComplete(server) {
104107
try {
105108
const [, result] = await pEvent(server, 'run_complete', {
106109
multiArgs: true,
107-
timeout: 10000,
110+
timeout: 50000,
108111
rejectionEvents: ['browser_error'],
109112
});
110113

test/helpers/mock.js

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,77 @@
1+
import {EventEmitter} from 'events';
12
import proxyquire from 'proxyquire';
23
import {spy, stub} from 'sinon';
34
import pify from 'pify';
4-
import chokidar from 'chokidar';
55

66
/**
7-
* @typedef {Object} Mock
7+
* @typedef {Object} MockPreprocessor
88
* @property {Function} preprocessor The preprocessor function.
99
* @property {Spy} debug A spied debug log function.
1010
* @property {Spy} error A spied error log function.
1111
* @property {Spy} info A spied info log function.
1212
* @property {Spy} refreshFiles A spied server's refreshFiles function.
13-
* @property {Spy} add A spied watcher's add function.
14-
* @property {Spy} unwatch A spied watcher's unwatch function.
15-
* @property {FSWatcher} watcher The preprocessor local watcher.
13+
* @property {EventEmitter} watcher The preprocessor local watcher.
14+
* @property {Spy} watcher.add A spied watcher's add function.
15+
* @property {Spy} watcher.unwatch A spied watcher's unwatch function.
1616
* @property {Stub} FSWatcher A stubbed local watcher constructor.
1717
*/
1818

19+
/**
20+
* @typedef {Object} MockFactory
21+
* @property {Oject} factory The preprocessor factory.
22+
* @property {Promise<EventEmitter>} watcher a Promise that resolves to preprocessor local watcher.
23+
* @property {Stub} FSWatcher A stubbed local watcher constructor.
24+
*/
25+
26+
/**
27+
* Create a mocked preprocessor factory.
28+
*
29+
* @method mockFactory
30+
* @param {Boolean} autoWatch `true` for autoWatch mode, `false` for a single run.
31+
* @return {MockFactory} mocked preprocessor factory and watcher.
32+
*/
33+
export function mockFactory(autoWatch) {
34+
const FSWatcher = stub();
35+
36+
return {
37+
factory: proxyquire('../../lib/index', {chokidar: {FSWatcher}}),
38+
watcher: pify(callback => {
39+
if (autoWatch) {
40+
return FSWatcher.callsFake(() => {
41+
const emitter = new EventEmitter();
42+
const add = spy();
43+
const unwatch = spy();
44+
45+
emitter.add = add;
46+
emitter.unwatch = unwatch;
47+
callback(null, emitter);
48+
return emitter;
49+
});
50+
} else {
51+
FSWatcher.returns(new EventEmitter());
52+
return callback(null);
53+
}
54+
})(),
55+
FSWatcher,
56+
};
57+
}
58+
1959
/**
2060
* Create a mocked preprocessor.
2161
*
2262
* @method mockPreprocessor
2363
* @param {Object} [args={}] custom preprocessor config to pass to the factory.
2464
* @param {Object} [config={}] Karma config to pass to the factory.
25-
* @return {Object} mocked preprocessor function and spies.
65+
* @return {MockPreprocessor} mocked preprocessor function and spies.
2666
*/
27-
export default function mockPreprocessor(args = {}, config = {}) {
28-
let add;
29-
let unwatch;
30-
let watcher;
31-
const FSWatcher = stub().callsFake((...watcherArgs) => {
32-
watcher = new chokidar.FSWatcher(...watcherArgs);
33-
add = spy(watcher, 'add');
34-
unwatch = spy(watcher, 'unwatch');
35-
return watcher;
36-
});
67+
export async function mockPreprocessor(args = {}, config = {}) {
3768
const debug = spy();
3869
const error = spy();
3970
const info = spy();
4071
const refreshFiles = spy();
72+
const {factory, watcher, FSWatcher} = mockFactory(config.autoWatch);
4173
const preprocessor = pify(
42-
proxyquire('../../lib/index', {chokidar: {FSWatcher}})['preprocessor:postcss'][1](
74+
factory['preprocessor:postcss'][1](
4375
args,
4476
config,
4577
{
@@ -51,5 +83,5 @@ export default function mockPreprocessor(args = {}, config = {}) {
5183
)
5284
);
5385

54-
return {preprocessor, debug, error, info, refreshFiles, add, unwatch, watcher, FSWatcher};
86+
return {preprocessor, debug, error, info, refreshFiles, watcher: await watcher, FSWatcher};
5587
}

test/helpers/utils.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,17 @@ export function tmp(filename) {
1515
return path.join('test/fixtures/.tmp', uuid(), filename || '');
1616
}
1717

18-
/**
19-
* Return a Promise that resolve after a delay.
20-
*
21-
* @method sleep
22-
* @param {Number} delay time after which to resolve the Promise.
23-
* @return {Promise} a Promise that resolve after a delay.
24-
*/
25-
export function sleep(delay) {
26-
// eslint-disable-next-line promise/avoid-new
27-
return new Promise(resolve => {
28-
setTimeout(resolve, delay);
29-
});
30-
}
31-
3218
/* eslint-disable no-magic-numbers */
3319
/**
3420
* Return a Promise that resolve when an event is emitted and reject after a timeout expire if the event is not emitted.
3521
*
3622
* @method waitFor
3723
* @param {Object} emitter object that emit events.
3824
* @param {string} event event to listen to.
39-
* @param {Number} [timeout=5000] maximum time to wait for the event to be emitted.
25+
* @param {Number} [timeout=30000] maximum time to wait for the event to be emitted.
4026
* @return {Promise} Promise tht resolve when the event is emitted.
4127
*/
42-
export function waitFor(emitter, event, timeout = 5000) {
28+
export function waitFor(emitter, event, timeout = 30000) {
4329
return pEvent(emitter, event, {timeout});
4430
}
4531

test/integration.test.js

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import path from 'path';
2-
import {utimes, copy, readFile, outputFile} from 'fs-extra';
2+
import {copy} from 'fs-extra';
33
import test from 'ava';
4-
import pTimeout from 'p-timeout';
54
import cssnano from 'cssnano';
65
import mixins from 'postcss-mixins';
76
import simpleVars from 'postcss-simple-vars';
@@ -21,21 +20,27 @@ test.after(() => {
2120
});
2221

2322
test('Compile css file', async t => {
24-
const {success, error, disconnected} = await run(['test/fixtures/basic.css', 'test/fixtures/styles.test.js'], {
25-
options: {plugins: [atImport, mixins, simpleVars, cssnano]},
26-
});
23+
const {success, error, disconnected, errMsg} = await run(
24+
['test/fixtures/basic.css', 'test/fixtures/styles.test.js'],
25+
{
26+
options: {plugins: [atImport, mixins, simpleVars, cssnano]},
27+
}
28+
);
2729

28-
t.ifError(error, 'Karma returned an error');
30+
t.ifError(error, `Karma returned the error: ${errMsg}`);
2931
t.ifError(disconnected, 'Karma disconnected');
3032
t.is(success, 1, 'Expected 1 test successful');
3133
});
3234

3335
test('Compile css file with custom preprocessor', async t => {
34-
const {success, error, disconnected} = await run(['test/fixtures/basic.custom.css', 'test/fixtures/styles.test.js'], {
35-
options: {plugins: [atImport, mixins, simpleVars, cssnano]},
36-
});
36+
const {success, error, disconnected, errMsg} = await run(
37+
['test/fixtures/basic.custom.css', 'test/fixtures/styles.test.js'],
38+
{
39+
options: {plugins: [atImport, mixins, simpleVars, cssnano]},
40+
}
41+
);
3742

38-
t.ifError(error, 'Karma returned an error');
43+
t.ifError(error, `Karma returned the error: ${errMsg}`);
3944
t.ifError(disconnected, 'Karma disconnected');
4045
t.is(success, 1, 'Expected 1 test successful');
4146
});
@@ -63,67 +68,25 @@ test('Re-compile css file when dependency is modified', async t => {
6368
copy('test/fixtures/with-partial.css', fixture),
6469
]);
6570

66-
const server = await watch(
71+
const {server, watcher} = await watch(
6772
[fixture.replace('fixtures', '*').replace('with', '+(with|nomatch)'), 'test/fixtures/styles.test.js'],
6873
{options: {plugins: [atImport({path: [includePath]}), mixins, simpleVars, cssnano]}}
6974
);
7075

7176
try {
72-
let {success, error, disconnected} = await waitForRunComplete(server);
77+
let {success, error, disconnected, errMsg} = await waitForRunComplete(server);
7378

74-
t.ifError(error, 'Karma returned an error');
79+
t.ifError(error, `Karma returned the error: ${errMsg}`);
7580
t.ifError(disconnected, 'Karma disconnected');
7681
t.is(success, 1, 'Expected 1 test successful');
7782

78-
utimes(partial, Date.now() / 1000, Date.now() / 1000);
79-
({success, error, disconnected} = await waitForRunComplete(server));
83+
watcher.emit('change', partial);
84+
({success, error, disconnected, errMsg} = await waitForRunComplete(server));
8085

81-
t.ifError(error, 'Karma returned an error');
86+
t.ifError(error, `Karma returned the error: ${errMsg}`);
8287
t.ifError(disconnected, 'Karma disconnected');
8388
t.is(success, 1, 'Expected 1 test successful');
8489
} finally {
8590
await server.emitAsync('exit');
8691
}
8792
});
88-
89-
test('Do not recompile css file when dependency is not imported anymore', async t => {
90-
const dir = path.resolve(tmp());
91-
const fixture = path.join(dir, 'with-partial.css');
92-
const includePath = path.join(dir, 'partials');
93-
const partial = path.join(includePath, 'partial.css');
94-
const partialAlt = path.join(includePath, 'partial-alt.css');
95-
const subPartial = path.join(includePath, 'sub-partial.css');
96-
97-
await Promise.all([
98-
copy('test/fixtures/partials/partial.css', partial),
99-
copy('test/fixtures/partials/partial.css', partialAlt),
100-
copy('test/fixtures/partials/sub-partial.css', subPartial),
101-
copy('test/fixtures/with-partial.css', fixture),
102-
]);
103-
104-
const server = await watch([fixture, 'test/fixtures/styles.test.js'], {
105-
options: {plugins: [atImport({path: [includePath]}), mixins, simpleVars, cssnano]},
106-
});
107-
108-
try {
109-
let {success, error, disconnected} = await waitForRunComplete(server);
110-
111-
t.ifError(error, 'Karma returned an error');
112-
t.ifError(disconnected, 'Karma disconnected');
113-
t.is(success, 1, 'Expected 1 test successful');
114-
115-
await outputFile(
116-
fixture,
117-
(await readFile(fixture)).toString().replace(`@import 'partial';`, `@import 'partial-alt';`)
118-
);
119-
({success, error, disconnected} = await waitForRunComplete(server));
120-
t.ifError(error, 'Karma returned an error');
121-
t.ifError(disconnected, 'Karma disconnected');
122-
t.is(success, 1, 'Expected 1 test successful');
123-
124-
utimes(partial, Date.now() / 1000, Date.now() / 1000);
125-
await t.throws(waitForRunComplete(server), pTimeout.TimeoutError);
126-
} finally {
127-
await server.emitAsync('exit');
128-
}
129-
});

0 commit comments

Comments
 (0)