522 lines
16 KiB
JavaScript
522 lines
16 KiB
JavaScript
const should = require('should');
|
|
const Vinyl = require('vinyl');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const sass = require('../index');
|
|
const rimraf = require('rimraf');
|
|
const gulp = require('gulp');
|
|
const sourcemaps = require('gulp-sourcemaps');
|
|
const postcss = require('gulp-postcss');
|
|
const autoprefixer = require('autoprefixer');
|
|
const tap = require('gulp-tap');
|
|
const globule = require('globule');
|
|
|
|
const createVinyl = (filename, contents) => {
|
|
const base = path.join(__dirname, 'scss');
|
|
const filePath = path.join(base, filename);
|
|
|
|
return new Vinyl({
|
|
cwd: __dirname,
|
|
base,
|
|
path: filePath,
|
|
contents: contents || fs.readFileSync(filePath),
|
|
});
|
|
};
|
|
|
|
const normaliseEOL = str => str.toString('utf8').replace(/\r\n/g, '\n');
|
|
|
|
describe('test helpers', () => {
|
|
it('should normalise EOL', (done) => {
|
|
should.equal(normaliseEOL('foo\r\nbar'), 'foo\nbar');
|
|
should.equal(normaliseEOL('foo\nbar'), 'foo\nbar');
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('gulp-sass -- async compile', () => {
|
|
it('should pass file when it isNull()', (done) => {
|
|
const stream = sass();
|
|
const emptyFile = {
|
|
isNull: () => true,
|
|
};
|
|
stream.on('data', (data) => {
|
|
data.should.equal(emptyFile);
|
|
done();
|
|
});
|
|
stream.write(emptyFile);
|
|
});
|
|
|
|
it('should emit error when file isStream()', (done) => {
|
|
const stream = sass();
|
|
const streamFile = {
|
|
isNull: () => false,
|
|
isStream: () => true,
|
|
};
|
|
stream.on('error', (err) => {
|
|
err.message.should.equal('Streaming not supported');
|
|
done();
|
|
});
|
|
stream.write(streamFile);
|
|
});
|
|
|
|
it('should compile an empty sass file', (done) => {
|
|
const sassFile = createVinyl('empty.scss');
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
should.equal(path.basename(cssFile.path), 'empty.css');
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'empty.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should compile a single sass file', (done) => {
|
|
const sassFile = createVinyl('mixins.scss');
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'mixins.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should compile multiple sass files', (done) => {
|
|
const files = [
|
|
createVinyl('mixins.scss'),
|
|
createVinyl('variables.scss'),
|
|
];
|
|
const stream = sass();
|
|
let mustSee = files.length;
|
|
let expectedPath = path.join('expected', 'mixins.css');
|
|
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
if (cssFile.path.indexOf('variables') !== -1) {
|
|
expectedPath = path.join('expected', 'variables.css');
|
|
}
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, expectedPath), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
|
|
mustSee -= 1;
|
|
if (mustSee <= 0) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
files.forEach((file) => {
|
|
stream.write(file);
|
|
});
|
|
});
|
|
|
|
it('should compile files with partials in another folder', (done) => {
|
|
const sassFile = createVinyl('inheritance.scss');
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'inheritance.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should handle sass errors', (done) => {
|
|
const errorFile = createVinyl('error.scss');
|
|
const stream = sass();
|
|
|
|
stream.on('error', (err) => {
|
|
// Error must include message body
|
|
err.message.indexOf('property "font" must be followed by a \':\'').should.not.equal(-1);
|
|
// Error must include file error occurs in
|
|
err.message.indexOf('test', 'scss', 'error.scss').should.not.equal(-1);
|
|
// Error must include line and column error occurs on
|
|
err.message.indexOf('on line 2').should.not.equal(-1);
|
|
// Error must include relativePath property
|
|
err.relativePath.should.equal(path.join('test', 'scss', 'error.scss'));
|
|
done();
|
|
});
|
|
stream.write(errorFile);
|
|
});
|
|
|
|
it('should preserve the original sass error message', (done) => {
|
|
const errorFile = createVinyl('error.scss');
|
|
const stream = sass();
|
|
|
|
stream.on('error', (err) => {
|
|
// Error must include original error message
|
|
err.messageOriginal.indexOf('property "font" must be followed by a \':\'').should.not.equal(-1);
|
|
// Error must not format or change the original error message
|
|
err.messageOriginal.indexOf('on line 2').should.equal(-1);
|
|
done();
|
|
});
|
|
stream.write(errorFile);
|
|
});
|
|
|
|
it('should compile a single sass file if the file name has been changed in the stream', (done) => {
|
|
const sassFile = createVinyl('mixins.scss');
|
|
// Transform file name
|
|
sassFile.path = path.join(path.join(__dirname, 'scss'), 'mixin--changed.scss');
|
|
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
cssFile.path.split(path.sep).pop().should.equal('mixin--changed.css');
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'mixins.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should preserve changes made in-stream to a Sass file', (done) => {
|
|
const sassFile = createVinyl('mixins.scss');
|
|
// Transform file name
|
|
sassFile.contents = Buffer.from(`/* Added Dynamically */${sassFile.contents.toString()}`);
|
|
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'mixins.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents))
|
|
.should.equal(`/* Added Dynamically */\n${normaliseEOL(actual)}`);
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should work with gulp-sourcemaps', (done) => {
|
|
const sassFile = createVinyl('inheritance.scss');
|
|
|
|
sassFile.sourceMap = '{' +
|
|
'"version": 3,' +
|
|
'"file": "scss/subdir/multilevelimport.scss",' +
|
|
'"names": [],' +
|
|
'"mappings": "",' +
|
|
'"sources": [ "scss/subdir/multilevelimport.scss" ],' +
|
|
'"sourcesContent": [ "@import ../inheritance;" ]' +
|
|
'}';
|
|
|
|
// Expected sources are relative to file.base
|
|
const expectedSources = [
|
|
'inheritance.scss',
|
|
'includes/_cats.scss',
|
|
'includes/_dogs.sass',
|
|
];
|
|
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile.sourceMap);
|
|
cssFile.sourceMap.sources.should.eql(expectedSources);
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should compile a single indented sass file', (done) => {
|
|
const sassFile = createVinyl('indent.sass');
|
|
const stream = sass();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'indent.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should parse files in sass and scss', (done) => {
|
|
const files = [
|
|
createVinyl('mixins.scss'),
|
|
createVinyl('indent.sass'),
|
|
];
|
|
const stream = sass();
|
|
let mustSee = files.length;
|
|
let expectedPath = path.join('expected', 'mixins.css');
|
|
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
if (cssFile.path.indexOf('indent') !== -1) {
|
|
expectedPath = path.join('expected', 'indent.css');
|
|
}
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, expectedPath), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
|
|
mustSee -= 1;
|
|
if (mustSee <= 0) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
files.forEach((file) => {
|
|
stream.write(file);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('gulp-sass -- sync compile', () => {
|
|
beforeEach((done) => {
|
|
rimraf(path.join(__dirname, 'results'), done);
|
|
});
|
|
|
|
it('should pass file when it isNull()', (done) => {
|
|
const stream = sass.sync();
|
|
const emptyFile = {
|
|
isNull: () => true,
|
|
};
|
|
stream.on('data', (data) => {
|
|
data.should.equal(emptyFile);
|
|
done();
|
|
});
|
|
stream.write(emptyFile);
|
|
});
|
|
|
|
it('should emit error when file isStream()', (done) => {
|
|
const stream = sass.sync();
|
|
const streamFile = {
|
|
isNull: () => false,
|
|
isStream: () => true,
|
|
};
|
|
stream.on('error', (err) => {
|
|
err.message.should.equal('Streaming not supported');
|
|
done();
|
|
});
|
|
stream.write(streamFile);
|
|
});
|
|
|
|
it('should compile a single sass file', (done) => {
|
|
const sassFile = createVinyl('mixins.scss');
|
|
const stream = sass.sync();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'mixins.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should compile multiple sass files', (done) => {
|
|
const files = [
|
|
createVinyl('mixins.scss'),
|
|
createVinyl('variables.scss'),
|
|
];
|
|
const stream = sass.sync();
|
|
let mustSee = files.length;
|
|
let expectedPath = path.join('expected', 'mixins.css');
|
|
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
if (cssFile.path.indexOf('variables') !== -1) {
|
|
expectedPath = path.join('expected', 'variables.css');
|
|
}
|
|
|
|
const actual = normaliseEOL(fs.readFileSync(path.join(__dirname, expectedPath), 'utf8'));
|
|
String(normaliseEOL(cssFile.contents)).should.equal(actual);
|
|
|
|
mustSee -= 1;
|
|
if (mustSee <= 0) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
files.forEach((file) => {
|
|
stream.write(file);
|
|
});
|
|
});
|
|
|
|
it('should compile files with partials in another folder', (done) => {
|
|
const sassFile = createVinyl('inheritance.scss');
|
|
const stream = sass.sync();
|
|
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile);
|
|
should.exist(cssFile.path);
|
|
should.exist(cssFile.relative);
|
|
should.exist(cssFile.contents);
|
|
|
|
const actual = fs.readFileSync(path.join(__dirname, 'expected', 'inheritance.css'), 'utf8');
|
|
String(normaliseEOL(cssFile.contents)).should.equal(normaliseEOL(actual));
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should handle sass errors', (done) => {
|
|
const errorFile = createVinyl('error.scss');
|
|
const stream = sass.sync();
|
|
|
|
stream.on('error', (err) => {
|
|
err.message.indexOf('property "font" must be followed by a \':\'').should.not.equal(-1);
|
|
err.relativePath.should.equal(path.join('test', 'scss', 'error.scss'));
|
|
done();
|
|
});
|
|
stream.write(errorFile);
|
|
});
|
|
|
|
it('should work with gulp-sourcemaps', (done) => {
|
|
const sassFile = createVinyl('inheritance.scss');
|
|
|
|
// Expected sources are relative to file.base
|
|
const expectedSources = [
|
|
'inheritance.scss',
|
|
'includes/_cats.scss',
|
|
'includes/_dogs.sass',
|
|
];
|
|
|
|
sassFile.sourceMap = '{' +
|
|
'"version": 3,' +
|
|
'"file": "scss/subdir/multilevelimport.scss",' +
|
|
'"names": [],' +
|
|
'"mappings": "",' +
|
|
'"sources": [ "scss/subdir/multilevelimport.scss" ],' +
|
|
'"sourcesContent": [ "@import ../inheritance;" ]' +
|
|
'}';
|
|
|
|
const stream = sass.sync();
|
|
stream.on('data', (cssFile) => {
|
|
should.exist(cssFile.sourceMap);
|
|
cssFile.sourceMap.sources.should.eql(expectedSources);
|
|
done();
|
|
});
|
|
stream.write(sassFile);
|
|
});
|
|
|
|
it('should work with gulp-sourcemaps and autoprefixer', (done) => {
|
|
const expectedSourcesBefore = [
|
|
'inheritance.scss',
|
|
'includes/_cats.scss',
|
|
'includes/_dogs.sass',
|
|
];
|
|
|
|
const expectedSourcesAfter = [
|
|
'includes/_cats.scss',
|
|
'includes/_dogs.sass',
|
|
'inheritance.scss',
|
|
];
|
|
|
|
gulp.src(path.join(__dirname, 'scss', 'inheritance.scss'))
|
|
.pipe(sourcemaps.init())
|
|
.pipe(sass.sync())
|
|
.pipe(tap((file) => {
|
|
should.exist(file.sourceMap);
|
|
file.sourceMap.sources.should.eql(expectedSourcesBefore);
|
|
}))
|
|
.pipe(postcss([autoprefixer()]))
|
|
.pipe(sourcemaps.write())
|
|
.pipe(gulp.dest(path.join(__dirname, 'results')))
|
|
.pipe(tap((file) => {
|
|
should.exist(file.sourceMap);
|
|
file.sourceMap.sources.should.eql(expectedSourcesAfter);
|
|
}))
|
|
.on('end', done);
|
|
});
|
|
|
|
it('should work with gulp-sourcemaps and a globbed source', (done) => {
|
|
const globPath = path.join(__dirname, 'scss', 'globbed');
|
|
const files = globule.find(path.join(__dirname, 'scss', 'globbed', '**', '*.scss'));
|
|
const filesContent = {};
|
|
|
|
files.forEach((file) => {
|
|
const source = path.normalize(path.relative(globPath, file));
|
|
filesContent[source] = fs.readFileSync(file, 'utf8');
|
|
});
|
|
|
|
gulp.src(path.join(__dirname, 'scss', 'globbed', '**', '*.scss'))
|
|
.pipe(sourcemaps.init())
|
|
.pipe(sass.sync())
|
|
.pipe(tap((file) => {
|
|
should.exist(file.sourceMap);
|
|
const actual = normaliseEOL(file.sourceMap.sourcesContent[0]);
|
|
const expected = normaliseEOL(filesContent[path.normalize(file.sourceMap.sources[0])]);
|
|
actual.should.eql(expected);
|
|
}))
|
|
.on('end', done);
|
|
});
|
|
|
|
it('should work with gulp-sourcemaps and autoprefixer with different file.base', (done) => {
|
|
const expectedSourcesBefore = [
|
|
'scss/inheritance.scss',
|
|
'scss/includes/_cats.scss',
|
|
'scss/includes/_dogs.sass',
|
|
];
|
|
|
|
const expectedSourcesAfter = [
|
|
'scss/includes/_cats.scss',
|
|
'scss/includes/_dogs.sass',
|
|
'scss/inheritance.scss',
|
|
];
|
|
|
|
gulp.src(path.join(__dirname, 'scss', 'inheritance.scss'), { base: 'test' })
|
|
.pipe(sourcemaps.init())
|
|
.pipe(sass.sync())
|
|
.pipe(tap((file) => {
|
|
should.exist(file.sourceMap);
|
|
file.sourceMap.sources.should.eql(expectedSourcesBefore);
|
|
}))
|
|
.pipe(postcss([autoprefixer()]))
|
|
.pipe(tap((file) => {
|
|
should.exist(file.sourceMap);
|
|
file.sourceMap.sources.should.eql(expectedSourcesAfter);
|
|
}))
|
|
.on('end', done);
|
|
});
|
|
|
|
it('should work with empty files', (done) => {
|
|
gulp.src(path.join(__dirname, 'scss', 'empty.scss'))
|
|
.pipe(sass.sync())
|
|
.pipe(gulp.dest(path.join(__dirname, 'results')))
|
|
.pipe(tap(() => {
|
|
try {
|
|
fs.statSync(path.join(__dirname, 'results', 'empty.css'));
|
|
} catch (e) {
|
|
should.fail(false, true, 'Empty file was produced');
|
|
}
|
|
}))
|
|
.on('end', done);
|
|
});
|
|
});
|