amercier/broccoli-sass-tree

View on GitHub
specs/lib/index.spec.js

Summary

Maintainability
B
5 hrs
Test Coverage
/* eslint-env mocha */

import { build, Builder, Node } from 'broccoli-fixture';
import { default as chai, expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import Promise from 'bluebird';
import { spy } from 'sinon';
import { readSync, writeSync } from 'fixturify';
import BroccoliSass from '../../src/lib';

chai.use(chaiAsPromised);

function wait(ms) {
  return (result) => new Promise(resolve => {
    setTimeout(() => {
      resolve(result);
    }, ms);
  });
}

describe('broccoli-sass-dir', () => {
  it('compiles .scss files', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode]);
    return expect(build(sass)).to.eventually.deep.equal({
      'app.css': 'html body {\n  font: Helvetica; }\n',
    });
  });

  it('resolves @import statements', () => {
    const inputNode = new Node({
      'app1.scss': 'html { body { font: Helvetica; } }',
      'app2.scss': '@import "app1";',
    });
    const sass = new BroccoliSass([inputNode]);
    return expect(build(sass)).to.eventually.deep.equal({
      'app1.css': 'html body {\n  font: Helvetica; }\n',
      'app2.css': 'html body {\n  font: Helvetica; }\n',
    });
  });

  it('does not render templates', () => {
    const inputNode = new Node({
      'my_app.scss': '@import "template";',
      '_template.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode]);
    return expect(build(sass)).to.eventually.deep.equal({
      'my_app.css': 'html body {\n  font: Helvetica; }\n',
    });
  });

  it('preserves directory structure', () => {
    const inputNode = new Node({
      'app1.scss': 'html { body { font: Helvetica; } }',
      subdir: {
        'app2.scss': '@import "../app1";',
      },
    });
    const sass = new BroccoliSass([inputNode]);
    return expect(build(sass)).to.eventually.deep.equal({
      'app1.css': 'html body {\n  font: Helvetica; }\n',
      subdir: {
        'app2.css': 'html body {\n  font: Helvetica; }\n',
      },
    });
  });

  it('throws an error on Syntax error', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } ]',
    });
    const sass = new BroccoliSass([inputNode]);
    return expect(build(sass)).to.eventually.be.rejectedWith(
      Error, 'Invalid CSS after "...t: Helvetica; }": expected "{", was "]"'
    );
  });

  it('generates .map sourcemaps', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode], {
      sassOptions: {
        sourceMap: true,
      },
    });
    return expect(build(sass)).to.eventually
      .have.property('app.map')
      .that.match(/\/app\.scss"/)
      .that.match(/"version": 3,/)
      .that.match(/"file": "app\.css",/)
      .that.match(/"sources": /)
      .and.match(/"mappings": /);
  });

  it('generates inline sourcemaps', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode], {
      sassOptions: {
        sourceMap: true,
        sourceMapEmbed: true,
      },
    });

    const result = build(sass);
    return Promise.all([
      expect(result).to.eventually.not.have.property('app.map'),
      expect(result).to.eventually
        .have.property('app.css')
        .that.match(/ sourceMappingURL=data:application\/json;base64,\w+ /),
    ]);
  });

  it('supports including the contents in the source maps information', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode], {
      sassOptions: {
        sourceMap: true,
        sourceMapContents: true,
      },
    });

    const result = build(sass);
    return Promise.all([
      expect(result).to.eventually
        .have.property('app.map')
        .that.not.match(/"app\.scss"/),
      expect(result).to.eventually
        .have.property('app.map')
        .that.match(/"sourcesContent": /)
        .and.match(/html { body { font: Helvetica; } }/),
    ]);
  });

  it('supports defining the source maps root', () => {
    const inputNode = new Node({
      'app.scss': 'html { body { font: Helvetica; } }',
    });
    const sass = new BroccoliSass([inputNode], {
      sassOptions: {
        sourceMap: true,
        sourceMapRoot: '/src/scss',
      },
    });

    return expect(build(sass)).to.eventually
      .have.property('app.map')
      .that.match(/"sourceRoot": "\/src\/scss"/);
  });

  it('rebuilds correctly', () => {
    const input = {
      'app1.scss': 'html { body { font: Helvetica; } }',
      'app2.scss': 'html { body { font: serif; } }',
      subdir: {
        'app3.scss': '@import "../app1";',
        'app4.scss': '@import "../app2";',
      },
    };
    const inputNode = new Node(input);
    const sass = new BroccoliSass([inputNode]);

    const fixtureBuilder = new Builder(sass);
    return fixtureBuilder.build()
    .then(wait(1000))
    .then(() => {
      spy(sass, 'render');
      writeSync(inputNode.outputPath, {
        'app1.scss': 'html { body { font: sans-serif; } }',
      });
      return readSync(inputNode.outputPath);
    })
    .then(() => sass.build())
    .then(() => {
      expect(readSync(sass.outputPath)).to.deep.equal({
        'app1.css': 'html body {\n  font: sans-serif; }\n',
        'app2.css': 'html body {\n  font: serif; }\n',
        subdir: {
          'app3.css': 'html body {\n  font: sans-serif; }\n',
          'app4.css': 'html body {\n  font: serif; }\n',
        },
      });
    });
  });

  it('rebuilds only modified files', () => {
    const input = {
      'app1.scss': 'html { body { font: Helvetica; } }',
      'app2.scss': 'html { body { font: serif; } }',
      subdir: {
        'app3.scss': '@import "../app1";',
        'app4.scss': '@import "../app2";',
      },
    };
    const inputNode = new Node(input);
    const sass = new BroccoliSass([inputNode]);

    const fixtureBuilder = new Builder(sass);
    return fixtureBuilder.build()
    .then(wait(1000))
    .then(() => {
      spy(sass, 'render');
      writeSync(inputNode.outputPath, {
        'app1.scss': 'html { body { font: sans-serif; } }',
      });
      return readSync(inputNode.outputPath);
    })
    .then(() => sass.build())
    .then(() => {
      expect(sass.render.callCount).to.equal(2);
    });
  });
});