8a14419856
This documents the option to use Dart Sass, and encourages users to explicitly choose their implementation, but it doesn't change the existing behavior.
540 lines
16 KiB
JavaScript
540 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 emit logError on sass error', (done) => {
|
|
const errorFile = createVinyl('error.scss');
|
|
const stream = sass();
|
|
|
|
stream.on('error', sass.logError);
|
|
stream.on('end', done);
|
|
stream.write(errorFile);
|
|
});
|
|
|
|
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 emit logError on sass error', (done) => {
|
|
const errorFile = createVinyl('error.scss');
|
|
const stream = sass.sync();
|
|
|
|
stream.on('error', sass.logError);
|
|
stream.on('end', 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);
|
|
});
|
|
});
|