├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── logo.png
├── package.json
├── src
├── funcResult.js
├── mixinResult.js
├── parsers.js
├── sassaby.js
├── types
│ ├── func.js
│ └── mixin.js
└── utilities.js
└── test
├── fixtures
├── ast.json
├── astMediaQuery.json
├── astMediaQueryFontFace.json
├── astNoSelectors.json
├── sample-with-blocks.scss
├── sample-with-dependencies.scss
├── sample-with-imports.scss
├── sample-with-variables-and-dependencies.scss
├── sample-with-variables.scss
├── sample.scss
└── variables.scss
├── funcResultTests.js
├── funcTests.js
├── integrationTests.js
├── integrationTestsWithBlocks.js
├── integrationTestsWithDependencies.js
├── integrationTestsWithImports.js
├── integrationTestsWithVariables.js
├── integrationTestsWithVariablesAndDependencies.js
├── mixinResultTests.js
├── mixinTests.js
├── parsersTests.js
├── sassabyTests.js
└── utilitiesTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "0.12"
5 | - "0.10"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Version 2.1.0
4 |
5 | * Adds support for mixins that take blocks. Adds `calledWithBlock` and `calledWithBlockAndArgs` functions to set this up.
6 | * Deprecates `calledWith` in favor of `calledWithArgs`.
7 | * Adds `called` for calls without arguments.
8 | * Adds `imports` and `doesNotImport` assertions at the file level for testing entry point files.
9 | * Adds `createFontFace` and `doesNotCreateFontFace` for standalone mixins to test creation of a `@font-face` rule.
10 | * Adds `createsMediaQuery` and `doesNotCreateMediaQuery` for standalone mixins to test creation of a `@media` directive.
11 | * Fixes bug for standalone mixins which may not create a selector (for example `@font-face` rules).
12 |
13 | ## Version 2.0.0
14 |
15 | * Performance upgrades
16 | * Decoupled mixin/function call from arguments. Arguments are now added with the `calledWith` function. Now it is easier to test different arguments of the same mixin/function and is clear when SASS compilation happens (easier to optimize your tests for speed).
17 | * Each sassaby test file is now an instance of the Sassaby class. This fixes a bug that was persisting test state between files in the suite.
18 |
19 | ## Version 1.0.2
20 | Initial Release
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Ryan Bahniuk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sassaby
2 | A Unit Testing Library for SASS
3 |
4 |
5 |
6 | Sassaby is a unit testing library for SASS mixins and functions. It is written in Node to provide a consistent interface with other front-end tests in your system and for easy integration into a build system. Its interface can be used with any Node testing library ([Mocha](https://www.npmjs.com/package/mocha "Mocha"), [Jasmine](https://www.npmjs.com/package/jasmine "Jasmine"), etc.).
7 |
8 | ## Table of Contents
9 | - [Installation](#installation)
10 | - [Setup](#setup)
11 | - [Dependencies](#dependencies)
12 | - [Features](#features)
13 | - [calledWithArgs](#calledwithargs)
14 | - [called](#called)
15 | - [calledWithBlock](#calledwithblock)
16 | - [calledWithBlockAndArgs](#calledwithblockandargs)
17 | - [Rules](#rules)
18 | - [Function Rules](#function-rules)
19 | - [equals](#equals)
20 | - [doesNotEqual](#doesnotequal)
21 | - [isTrue](#istrue)
22 | - [isFalse](#isfalse)
23 | - [isTruthy](#istruthy)
24 | - [isFalsy](#isfalsy)
25 | - [Standalone Mixin Rules](#standalone-mixin-rules)
26 | - [createsSelector](#createsselector)
27 | - [doesNotCreateSelector](#doesnotcreateselector)
28 | - [createsMediaQuery](#createsmediaquery)
29 | - [doesNotCreateMediaQuery](#doesnotcreatemediaquery)
30 | - [createsFontFace](#createsfontface)
31 | - [doesNotCreateFontFace](#doesnotcreatefontface)
32 | - [hasNumDeclarations](#hasnumdeclarations)
33 | - [declares](#declares)
34 | - [doesNotDeclare](#doesnotdeclare)
35 | - [equals](#equals-1)
36 | - [doesNotEqual](#doesnotequal-1)
37 | - [calls](#calls)
38 | - [doesNotCall](#doesnotcall)
39 | - [Included Mixin Rules](#included-mixin-rules)
40 | - [hasNumDeclarations](#hasnumdeclarations-1)
41 | - [declares](#declares-1)
42 | - [doesNotDeclare](#doesnotdeclare-1)
43 | - [equals](#equals-2)
44 | - [doesNotEqual](#doesnotequal-2)
45 | - [calls](#calls-1)
46 | - [doesNotCall](#doesnotcall-1)
47 | - [Testing Imports](#testing-imports)
48 | - [imports](#imports)
49 | - [doesNotImport](#doesnotimport)
50 | - [Contributing](#contributing)
51 | - [License](#license)
52 |
53 |
54 | ## Installation
55 |
56 | Install via npm:
57 |
58 | ```sh
59 | npm install --save-dev sassaby
60 | ```
61 |
62 | ## Setup
63 |
64 | Setting up Sassaby is simple with easy integration into your existing Javascript testing library. After installation, simply require it at the top of the file and instantiate a new object with the .sass or .scss file that you want to include. Here is a sample file using [Mocha](https://www.npmjs.com/package/mocha "Mocha").
65 |
66 | ```js
67 | var path = require('path');
68 | var Sassaby = require('sassaby');
69 |
70 | describe('sample.scss', function() {
71 | var file = path.resolve(__dirname, 'sample.scss');
72 | var sassaby = new Sassaby(file);
73 |
74 | describe('appearance', function() {
75 | it('should have a webkit prefixed declaration', function() {
76 | sassaby.includedMixin('appearance').calledWith('button').declares('-webkit-appearance', 'button');
77 | });
78 | });
79 | });
80 | ```
81 |
82 | Note that the Sassaby constructor takes the absolute path to the SASS file. We recommend using Node's `path` and `__dirname` (which gives you the directory of the test file) plus the remaining path here. Also, note that this file must **ONLY** include SASS function and mixin declarations. Any code that compiles to CSS in this file will cause Sassaby's parsers to give inconsistent results.
83 |
84 | ## Dependencies
85 |
86 | We recommend testing SASS files in isolation. However, depending on the setup of your SASS import tree some functions and mixins may rely on externally declared variables, mixins, or functions. In this case, you can pass an options object to the Sassaby constructor with `variables` and/or `dependencies` defined. Here is the sample file with these options:
87 |
88 | ```js
89 | var path = require('path');
90 | var Sassaby = require('sassaby');
91 |
92 | describe('sample.scss', function() {
93 | var file = path.resolve(__dirname, 'sample.scss');
94 | var sassaby = new Sassaby(file, {
95 | variables: {
96 | 'grid-columns': 12
97 | },
98 | dependencies: [
99 | path.resolve(__dirname, 'need-this-to-compile.scss')
100 | ]
101 | });
102 |
103 | describe('#appearance', function() {
104 | it('should have a webkit prefixed declaration', function() {
105 | sassaby.includedMixin('appearance').calledWith('button').declares('-webkit-appearance', 'button');
106 | });
107 | });
108 | });
109 | ```
110 |
111 | `variables` should be an object with string keys. It will declare each key-value pair as a SASS variable before compiling the given function/mixin.
112 | `dependencies` should be an array of file paths to be imported into the compiled SASS. We recommend using the same approach (with `path` and `__dirname`) that is used with setting the file path.
113 |
114 | ## Features
115 |
116 | Sassaby breaks down testable features into four categories:
117 |
118 | * Functions
119 | * Standalone Mixins
120 | * Included Mixins
121 |
122 | Functions are your typical SASS functions, defined like this:
123 |
124 | ```scss
125 | @function rems($pxsize, $rembase) {
126 | @return ($pxsize/$rembase)+rem;
127 | }
128 | ```
129 |
130 | Standalone Mixins are mixins that define new rules, like this:
131 |
132 | ```scss
133 | @mixin align-right($label) {
134 | .align-right-#{$label} {
135 | justify-content: flex-end;
136 | }
137 | }
138 | ```
139 |
140 | Included Mixins are mixins that do not define new rules, just declarations that should be placed into existing rules. For example:
141 |
142 | ```scss
143 | @mixin appearance($value) {
144 | -webkit-appearance: $value;
145 | -moz-appearance: $value;
146 | appearance: $value;
147 | }
148 | ```
149 |
150 | Each of these categories can be defined by a respective function defined on an instance of `Sassaby`. For example, you can set up each of them with this syntax:
151 |
152 | ```js
153 | var sassaby = new Sassaby(filePath);
154 | var testFunction = sassaby.func('rems');
155 | var testStandaloneMixin = sassaby.standaloneMixin('align-right');
156 | var testIncludedMixin = sassaby.includedMixin('appearance');
157 | ```
158 |
159 | These functions will read the given file (with variables and dependencies if given) and return an object that will takes one of the "called" functions documented below. SASS compilation will occur at this step.
160 |
161 | ### calledWithArgs
162 | Calls the mixin or function with the given arguments.
163 | ```js
164 | sassaby.func('rems').calledWithArgs('32px', '16px');
165 | sassaby.standaloneMixin('align-right').calledWithArgs('md');
166 | sassaby.includedMixin('appearance').calledWithArgs('button')
167 | ```
168 |
169 | ### called
170 | Calls the mixin or function with no arguments.
171 | ```js
172 | sassaby.func('rems').called();
173 | sassaby.standaloneMixin('align-right').called();
174 | sassaby.includedMixin('appearance').called();
175 | ```
176 |
177 | ### calledWithBlock
178 | Calls the mixin with a block. This is only available on mixins and the block is to be given as a string without wrapping brackets.
179 | ```js
180 | sassaby.standaloneMixin('align-right').calledWithBlock('.test { color: red; }');
181 | sassaby.includedMixin('appearance').calledWithBlock('.test { color: red; }')
182 | ```
183 |
184 | ### calledWithBlockAndArgs
185 | Calls the mixin with a block and arguments. This is only available on mixins and the block is to be given as a string without wrapping brackets. The block is always the first argument of this function and the mixin arguments will follow.
186 | ```js
187 | sassaby.standaloneMixin('align-right').calledWithBlock('.test { color: red; }', true, 1);
188 | sassaby.includedMixin('appearance').calledWithBlock('.test { color: red; }', true, 1)
189 | ```
190 |
191 | **[:arrow_up: back to top](#table-of-contents)**
192 |
193 | ## Rules
194 |
195 | Each of these types has their own set of functions, or rules, that assert certain conditions on the result of the function or mixin. The arguments of these rules are normalized to match the output from the SASS compilation, so it can be formatted however you wish as long as it is compilable SASS.
196 |
197 | ### Function Rules
198 |
199 |
200 | #### equals
201 | Asserts that the function output equals a certain value.
202 | ```js
203 | sassaby.func('rems').calledWithArgs('32px', '16px').equals('2rem');
204 | ```
205 |
206 | #### doesNotEqual
207 | Assert that the function output does not equal a certain value.
208 | ```js
209 | sassaby.func('rems').calledWithArgs('32px', '16px').doesNotEqual('3rem');
210 | ```
211 |
212 | #### isTrue
213 | Assert that the function output equals true.
214 | ```js
215 | sassaby.func('returns-true').calledWithArgs(true).isTrue();
216 | ```
217 |
218 | #### isFalse
219 | Assert that the function output equals false.
220 | ```js
221 | sassaby.func('returns-false').calledWithArgs(false).isFalse();
222 | ```
223 |
224 | #### isTruthy
225 | Assert that the function output is a truthy value in SASS. Keep in mind that this is SASS truthy, not Javascript truthy.
226 | ```js
227 | sassaby.func('returns-truthy').calledWithArgs('string').isTruthy();
228 | ```
229 |
230 | #### isFalsy
231 | Assert that the function output is a falsy value in SASS. Keep in mind that this is SASS truthy, not Javascript truthy.
232 | ```js
233 | sassaby.func('returns-falsy').calledWithArgs(null).isFalsy();
234 | ```
235 |
236 | **[:arrow_up: back to top](#table-of-contents)**
237 |
238 | ### Standalone Mixin Rules
239 |
240 |
241 | #### createsSelector
242 | Assert that the mixin creates the given selector.
243 | ```js
244 | sassaby.standaloneMixin('align-right').calledWithArgs('md').createsSelector('.align-right-md');
245 | ```
246 |
247 | #### doesNotCreateSelector
248 | Assert that the mixin does not create the given selector.
249 | ```js
250 | sassaby.standaloneMixin('align-right').calledWithArgs('md').doesNotCreateSelector('.align-right-lg');
251 | ```
252 |
253 | #### createsMediaQuery
254 | Assert that the mixin creates a media query with the given string.
255 | ```js
256 | sassaby.standaloneMixin('make-button').calledWithArgs('200px').createsMediaQuery('screen and (max-width: 200px)');
257 | ```
258 |
259 | #### doesNotCreateMediaQuery
260 | Assert that the mixin does not create a media query with the given string.
261 | ```js
262 | sassaby.standaloneMixin('make-button').calledWithArgs('200px').doesNotCreateMediaQuery('screen and (max-width: 400px)');
263 | ```
264 |
265 | #### createsFontFace
266 | Assert that the mixin creates a font-face rule.
267 | ```js
268 | sassaby.standaloneMixin('make-font-face').calledWithArgs('helvetica').createsFontFace();
269 | ```
270 |
271 | #### doesNotCreateFontFace
272 | Assert that the mixin does not create a font-face rule.
273 | ```js
274 | sassaby.standaloneMixin('align-right').calledWithArgs('md').doesNotCreateFontFace();
275 | ```
276 |
277 | #### hasNumDeclarations
278 | Assert that the mixin creates the given number of declarations.
279 | ```js
280 | sassaby.standaloneMixin('align-right').calledWithArgs('md').hasNumDeclarations(1);
281 | ```
282 |
283 | #### declares
284 | Assert that the mixin makes a declaration of the given rule-property pair.
285 | ```js
286 | sassaby.standaloneMixin('align-right').calledWithArgs('md').declares('justify-content', 'flex-end');
287 | ```
288 |
289 | #### doesNotDeclare
290 | Assert that the mixin does not make a declaration of the given rule-property pair.
291 | ```js
292 | sassaby.standaloneMixin('align-right').calledWithArgs('md').doesNotDeclare('text-align', 'right');
293 | ```
294 |
295 | #### equals
296 | Assert that the mixin output equals the given string.
297 | ```js
298 | sassaby.standaloneMixin('align-right').calledWithArgs('md').equals('.align-right-md { justify-content: flex-end; }');
299 | ```
300 |
301 | #### doesNotEqual
302 | Assert that the mixin output does not equal the given string.
303 | ```js
304 | sassaby.standaloneMixin('align-right').calledWithArgs('md').doesNotEqual('.align-right-lg { justify-content: flex-end; }');
305 | ```
306 |
307 | #### calls
308 | Assert that the mixin calls another mixin.
309 | ```js
310 | sassaby.standaloneMixin('build-alignments').calledWithArgs('md').calls('align-right(md)');
311 | ```
312 |
313 | #### doesNotCall
314 | Assert that the mixin does not call another mixin.
315 | ```js
316 | sassaby.standaloneMixin('build-alignments').calledWithArgs('md').doesNotCall('align-right(lg)');
317 | ```
318 |
319 | **[:arrow_up: back to top](#table-of-contents)**
320 |
321 | ### Included Mixin Rules
322 |
323 |
324 | #### hasNumDeclarations
325 | Assert that the mixin creates the given number of declarations.
326 | ```js
327 | sassaby.includedMixin('appearance').calledWithArgs('button').hasNumDeclarations(3);
328 | ```
329 |
330 | #### declares
331 | Assert that the mixin makes a declaration of the given rule-property pair.
332 | ```js
333 | sassaby.includedMixin('appearance').calledWithArgs('button').declares('-webkit-appearance', 'button');
334 | ```
335 |
336 | #### doesNotDeclare
337 | Assert that the mixin does not make a declaration of the given rule-property pair.
338 | ```js
339 | sassaby.includedMixin('appearance').calledWithArgs('button').doesNotDeclare('-o-appearance', 'button');
340 | ```
341 |
342 | #### equals
343 | Assert that the mixin output equals the given string.
344 | ```js
345 | sassaby.includedMixin('appearance').calledWithArgs('button').equals('-webkit-appearance: button; -moz-appearance: button; appearance: button;');
346 | ```
347 |
348 | #### doesNotEqual
349 | Assert that the mixin output does not equal the given string.
350 | ```js
351 | sassaby.includedMixin('appearance').calledWithArgs('button').doesNotEqual('appearance: button;');
352 | ```
353 |
354 | #### calls
355 | Assert that the mixin calls another mixin.
356 | ```js
357 | sassaby.includedMixin('appearance').calledWithArgs('button').calls('prefixer(button)');
358 | ```
359 |
360 | #### doesNotCall
361 | Assert that the mixin does not call another mixin.
362 | ```js
363 | sassaby.includedMixin('appearance').calledWithArgs('button').doesNotCall('prefixer(-webkit-button)');
364 | ```
365 |
366 | **[:arrow_up: back to top](#table-of-contents)**
367 |
368 | ### Testing Imports
369 |
370 | Often your SASS project will have a single entry point from where all other files are imported. Sassaby exposes two assertion methods on the sassaby object itself to test this. These two methods take the same path that would be included in the `@import` statement in your SASS file.
371 |
372 |
373 | #### imports
374 | Assert that the file imports the given path.
375 | ```js
376 | sassaby.imports('variables');
377 | ```
378 |
379 | #### doesNotImport
380 | Assert that the file does not import the given path.
381 | ```js
382 | sassaby.doesNotImport('nope');
383 | ```
384 |
385 | ## Contributing
386 |
387 | Pull requests are welcome. If you add functionality, then please add unit tests
388 | to cover it. Continuous Integration is handled by [Travis](https://travis-ci.org/ryanbahniuk/sassaby "Travis").
389 |
390 | ## License
391 |
392 | MIT © Ryan Bahniuk
393 |
394 | [ci]: https://travis-ci.org/ryanbahniuk/sassaby
395 | [npm]: https://www.npmjs.com/package/sassaby
396 |
397 | **[:arrow_up: back to top](#table-of-contents)**
398 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryanbahniuk/sassaby/1a1c2aa07dccd9a1af2773386453b305bfdba47a/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sassaby",
3 | "version": "2.1.1",
4 | "description": "A unit testing library for SASS",
5 | "keywords": ["sass", "scss", "testing", "unit testing", "bdd", "tdd", "test"],
6 | "author": "Ryan Bahniuk ",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/ryanbahniuk/sassaby"
11 | },
12 | "main": "src/sassaby",
13 | "dependencies": {
14 | "css": "^2.2.0",
15 | "cssmin": "^0.4.3",
16 | "mocha": "^2.2.5",
17 | "node-sass": "^3.1.2"
18 | },
19 | "devDependencies": {
20 | "proxyquire": "^1.5.0",
21 | "sinon": "^1.14.1"
22 | },
23 | "scripts": {
24 | "test": "NODE_ENV=test mocha"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/funcResult.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var utilities = require('./utilities');
5 |
6 | function wrapFunctionWithArgs(call, args) {
7 | var argString = utilities.concatArgs(args);
8 | return wrapFunction(call + '(' + argString + ')');
9 | }
10 |
11 | function wrapTruthyFunctionWithArgs(call, args) {
12 | var argString = utilities.concatArgs(args);
13 | return sassTruthy() + wrapFunction('truthy(' + call + '(' + argString + '))');
14 | }
15 |
16 | function wrapTruthyFunction(call) {
17 | return sassTruthy() + wrapFunction('truthy(' + call + ')');
18 | }
19 |
20 | function wrapFunction(call) {
21 | return '.test{content:' + call + '}';
22 | }
23 |
24 | function sassTruthy() {
25 | return '@function truthy($value) { @if $value { @return true } @else { @return false } }';
26 | }
27 |
28 | function compileCss(file, call, args) {
29 | if (args && args.length > 0) {
30 | return utilities.createCss(file, wrapFunctionWithArgs(call, args));
31 | } else {
32 | return utilities.createCss(file, wrapFunction(call));
33 | }
34 | }
35 |
36 | function compileTruthyCss(file, call, args) {
37 | if (args && args.length > 0) {
38 | return utilities.createCss(file, wrapTruthyFunctionWithArgs(call, args));
39 | } else {
40 | return utilities.createCss(file, wrapTruthyFunction(call));
41 | }
42 | }
43 |
44 | function FuncResult(file, call, args) {
45 | this.css = compileCss(file, call, args);
46 | this.truthyCss = compileTruthyCss(file, call, args);
47 | }
48 |
49 | FuncResult.prototype = {
50 | equals: function(result) {
51 | var message = 'Function: ' + this.call + ' does not equal ' + result + '.';
52 | assert.equal(this.css, wrapFunction(result), message);
53 | },
54 |
55 | doesNotEqual: function(result) {
56 | var message = 'Function: ' + this.call + ' equals ' + result + '.';
57 | assert.notEqual(this.css, wrapFunction(result), message);
58 | },
59 |
60 | isTrue: function() {
61 | var message = 'Function does not equal true.';
62 | assert.equal(this.css, wrapFunction(true), message);
63 | },
64 |
65 | isFalse: function() {
66 | var message = 'Function does not equal false.';
67 | assert.equal(this.css, wrapFunction(false), message);
68 | },
69 |
70 | isTruthy: function() {
71 | var message = 'Function is not truthy.';
72 | assert.equal(this.truthyCss, wrapFunction(true), message);
73 | },
74 |
75 | isFalsy: function() {
76 | var message = 'Function is not falsy.';
77 | assert.equal(this.truthyCss, wrapFunction(false), message);
78 | }
79 | };
80 |
81 | if (process.env.NODE_ENV === 'test') {
82 | FuncResult.wrapFunctionWithArgs = wrapFunctionWithArgs;
83 | FuncResult.wrapTruthyFunctionWithArgs = wrapTruthyFunctionWithArgs;
84 | FuncResult.wrapTruthyFunction = wrapTruthyFunction;
85 | FuncResult.wrapFunction = wrapFunction;
86 | FuncResult.sassTruthy = sassTruthy;
87 | FuncResult.compileCss = compileCss;
88 | FuncResult.compileTruthyCss = compileTruthyCss;
89 | }
90 |
91 | module.exports = FuncResult;
92 |
--------------------------------------------------------------------------------
/src/mixinResult.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var cssmin = require('cssmin');
5 | var utilities = require('./utilities');
6 | var parsers = require('./parsers');
7 |
8 | function wrapMixinWithArgs(type, call, args) {
9 | var argString = utilities.concatArgs(args);
10 | var includeString = '@include ' + call + '(' + argString + ');';
11 | return type === 'included' ? '.test{' + includeString + '}' : includeString;
12 | }
13 |
14 | function wrapMixinWithBlock(type, call, block) {
15 | var includeString = '@include ' + call + '{' + block + '}';
16 | return type === 'included' ? '.test{' + includeString + '}' : includeString;
17 | }
18 |
19 | function wrapMixinWithBlockAndArgs(type, call, block, args) {
20 | var argString = utilities.concatArgs(args);
21 | var includeString = '@include ' + call + '(' + argString + ') ' + '{' + block + '}';
22 | return type === 'included' ? '.test{' + includeString + '}' : includeString;
23 | }
24 |
25 | function wrapMixin(type, call) {
26 | var includeString = '@include ' + call + ';';
27 | return type === 'included' ? '.test{' + includeString + '}' : includeString;
28 | }
29 |
30 | function wrapOutput(type, css) {
31 | if (type === 'included') {
32 | return cssmin('.test{' + css + '}');
33 | } else {
34 | return cssmin(css);
35 | }
36 | }
37 |
38 | function unwrapOutput(type, css) {
39 | var unwrapped = css;
40 | if (type === 'included') {
41 | unwrapped = unwrapped.replace(/^.test{/g, '').replace(/\}$/g, '');
42 | }
43 |
44 | return unwrapped.replace(/;$/g, '');
45 | }
46 |
47 | function compileCss(file, type, call, args, block) {
48 | if (args && args.length > 0 && block) {
49 | return utilities.createCss(file, wrapMixinWithBlockAndArgs(type, call, block, args));
50 | } else if (args && args.length > 0) {
51 | return utilities.createCss(file, wrapMixinWithArgs(type, call, args));
52 | } else if (block) {
53 | return utilities.createCss(file, wrapMixinWithBlock(type, call, block));
54 | } else {
55 | return utilities.createCss(file, wrapMixin(type, call));
56 | }
57 | }
58 |
59 | function MixinResult(type, file, call, args, block) {
60 | this.type = type;
61 | this.file = file;
62 | this.css = compileCss(file, type, call, args, block);
63 | this.ast = utilities.createAst(this.css);
64 | }
65 |
66 | MixinResult.prototype = {
67 | createsSelector: function(selector) {
68 | if (this.type === 'included') {
69 | throw 'createsSelector is not available for included mixins.';
70 | }
71 | var message = 'Could not find selector ' + selector + ' in mixin output.';
72 | assert(parsers.hasSelector(this.ast, selector), message);
73 | },
74 |
75 | doesNotCreateSelector: function(selector) {
76 | if (this.type === 'included') {
77 | throw 'doesNotCreateSelector is not available for included mixins.';
78 | }
79 | var message = 'Mixin created selector ' + selector + '.';
80 | assert(!parsers.hasSelector(this.ast, selector), message);
81 | },
82 |
83 | createsMediaQuery: function(mediaQuery) {
84 | var rule = parsers.findMedia(this.ast);
85 | var media = rule ? rule.media : '';
86 | var message = 'Could not find a media query rule with the value: ' + mediaQuery + '.';
87 | assert.equal(media, cssmin(mediaQuery), message);
88 | },
89 |
90 | doesNotCreateMediaQuery: function(mediaQuery) {
91 | var rule = parsers.findMedia(this.ast);
92 | var media = rule ? rule.media : '';
93 | var message = 'Found a media query rule with the value: ' + mediaQuery + '.';
94 | assert.notEqual(media, cssmin(mediaQuery), message);
95 | },
96 |
97 | createsFontFace: function() {
98 | if (this.type === 'included') {
99 | throw 'createsFontFace is not available for included mixins.';
100 | }
101 | var message = 'Could not find a font-face rule.';
102 | assert(parsers.hasFontFace(this.ast), message);
103 | },
104 |
105 | doesNotCreateFontFace: function() {
106 | if (this.type === 'included') {
107 | throw 'doesNotCreateFontFace is not available for included mixins.';
108 | }
109 | var message = 'Mixin created a font-face rule.';
110 | assert(!parsers.hasFontFace(this.ast), message);
111 | },
112 |
113 | hasNumDeclarations: function(num) {
114 | var numDeclarations = parsers.countDeclarations(this.ast);
115 | var message = 'Mixin has ' + numDeclarations + ' declarations, but you gave ' + num + '.';
116 | assert.equal(numDeclarations, num, message);
117 | },
118 |
119 | declares: function(property, value) {
120 | var declaration = parsers.findDeclaration(this.ast, property);
121 | var declarationValue = declaration ? utilities.scrubQuotes(declaration.value) : '';
122 | var message = 'Value: ' + declarationValue + ' does not equal value: ' + value + '.';
123 | assert.equal(declarationValue, value.toString(), message);
124 | },
125 |
126 | doesNotDeclare: function(property, value) {
127 | var declaration = parsers.findDeclaration(this.ast, property);
128 | var declarationValue = declaration ? utilities.scrubQuotes(declaration.value) : '';
129 | var message = 'Value: ' + declarationValue + ' equals value: ' + value + '.';
130 | assert.notEqual(declarationValue, value.toString(), message);
131 | },
132 |
133 | equals: function(output) {
134 | var wrappedOutput = wrapOutput(this.type, output);
135 | var message = 'Mixin output is ' + this.css + ' and you gave ' + wrappedOutput + '.';
136 | assert.equal(this.css, wrappedOutput, message);
137 | },
138 |
139 | doesNotEqual: function(output) {
140 | var wrappedOutput = wrapOutput(this.type, output);
141 | var message = 'Mixin output equals ' + output + '.';
142 | assert.notEqual(this.css, wrappedOutput, message);
143 | },
144 |
145 | calls: function(mixin) {
146 | var mixinCss = utilities.createCss(this.file, wrapMixin(this.type, mixin));
147 | var message = 'Could not find the output from ' + mixin + ' in mixin.';
148 | assert(this.css.indexOf(unwrapOutput(this.type, mixinCss)) > -1, message);
149 | },
150 |
151 | doesNotCall: function(mixin) {
152 | var mixinCss = utilities.createCss(this.file, wrapMixin(this.type, mixin));
153 | var message = 'Mixin called ' + mixin + '.';
154 | assert(this.css.indexOf(unwrapOutput(this.type, mixinCss)) === -1, message);
155 | }
156 | };
157 |
158 | if (process.env.NODE_ENV === 'test') {
159 | MixinResult.wrapMixinWithArgs = wrapMixinWithArgs;
160 | MixinResult.wrapMixinWithBlock = wrapMixinWithBlock;
161 | MixinResult.wrapMixinWithBlockAndArgs = wrapMixinWithBlockAndArgs;
162 | MixinResult.wrapMixin = wrapMixin;
163 | MixinResult.wrapOutput = wrapOutput;
164 | MixinResult.unwrapOutput = unwrapOutput;
165 | MixinResult.compileCss = compileCss;
166 | }
167 |
168 | module.exports = MixinResult;
169 |
--------------------------------------------------------------------------------
/src/parsers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function escapeCharacters(string) {
4 | var specials = ['/', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}'];
5 | var pattern = '([' + specials.join('') + '])';
6 | var re = new RegExp(pattern, 'g');
7 | return string.replace(re, '\\$1');
8 | }
9 |
10 | function hasSelectorValue(rule, selectorValue) {
11 | var found = false;
12 |
13 | if (rule.selectors) {
14 | rule.selectors.forEach(function(selector) {
15 | if (selector === selectorValue) {
16 | found = true;
17 | }
18 | });
19 | }
20 |
21 | return found;
22 | }
23 |
24 | function findDeclarationProperty(rule, declarationProperty) {
25 | var foundDeclaration;
26 |
27 | if (rule.declarations) {
28 | rule.declarations.forEach(function(declaration) {
29 | if (declaration.property === declarationProperty) {
30 | foundDeclaration = declaration;
31 | }
32 | });
33 | }
34 |
35 | return foundDeclaration;
36 | }
37 |
38 | function isFontFace(rule) {
39 | if (rule.type === 'font-face') {
40 | return true;
41 | }
42 | return false;
43 | }
44 |
45 | var Parsers = {
46 | countDeclarations: function(ast) {
47 | var count = 0;
48 |
49 | ast.stylesheet.rules.forEach(function(rule) {
50 | if (rule.type === 'media') {
51 | rule.rules.forEach(function(rule) {
52 | count = count + rule.declarations.length;
53 | });
54 | } else {
55 | count = count + rule.declarations.length;
56 | }
57 | });
58 | return count;
59 | },
60 |
61 | findDeclaration: function(ast, property) {
62 | var found;
63 |
64 | ast.stylesheet.rules.forEach(function(rule) {
65 | if (rule.type === 'media') {
66 | rule.rules.forEach(function(rule) {
67 | found = found || findDeclarationProperty(rule, property);
68 | });
69 | } else {
70 | found = found || findDeclarationProperty(rule, property);
71 | }
72 | });
73 |
74 | return found;
75 | },
76 |
77 | findMedia: function(ast) {
78 | var found = [];
79 |
80 | ast.stylesheet.rules.forEach(function(rule) {
81 | if (rule.type === 'media') {
82 | found.push(rule);
83 | }
84 | });
85 |
86 | return found[0];
87 | },
88 |
89 | hasSelector: function(ast, selectorValue) {
90 | var found = false;
91 |
92 | ast.stylesheet.rules.forEach(function(rule) {
93 | if (rule.type === 'media') {
94 | rule.rules.forEach(function(rule) {
95 | found = found || hasSelectorValue(rule, selectorValue);
96 | });
97 | } else {
98 | found = found || hasSelectorValue(rule, selectorValue);
99 | }
100 | });
101 |
102 | return found;
103 | },
104 |
105 | hasFontFace: function(ast) {
106 | var found = false;
107 |
108 | ast.stylesheet.rules.forEach(function(rule) {
109 | if (rule.type === 'media') {
110 | rule.rules.forEach(function(rule) {
111 | found = found || isFontFace(rule);
112 | });
113 | } else {
114 | found = found || isFontFace(rule);
115 | }
116 | });
117 |
118 | return found;
119 | },
120 |
121 | hasImport: function(sass, importName) {
122 | var pattern = new RegExp('@import[^;]*[\'\"]' + escapeCharacters(importName) + '[\'\"].*;');
123 | return !!sass.match(pattern);
124 | }
125 | };
126 |
127 | if (process.env.NODE_ENV === 'test') {
128 | Parsers.escapeCharacters = escapeCharacters;
129 | Parsers.hasSelectorValue = hasSelectorValue;
130 | Parsers.findDeclarationProperty = findDeclarationProperty;
131 | Parsers.isFontFace = isFontFace;
132 | }
133 |
134 | module.exports = Parsers;
135 |
--------------------------------------------------------------------------------
/src/sassaby.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var assert = require('assert');
5 | var Mixin = require('./types/mixin');
6 | var Func = require('./types/func');
7 | var parsers = require('./parsers');
8 |
9 | function setVariables(varz) {
10 | var sassVariables = '';
11 | for (var variableName in varz) {
12 | sassVariables = sassVariables + '$' + variableName + ':' + varz[variableName] + ';';
13 | }
14 | return sassVariables;
15 | }
16 |
17 | function setDependencies(dependencies) {
18 | var sassImports = '';
19 | dependencies.forEach(function(fileName) {
20 | sassImports = sassImports + "@import '" + fileName + "';";
21 | });
22 | return sassImports;
23 | }
24 |
25 | function Sassaby(path, options) {
26 | options = options || {};
27 | this.path = path;
28 | this.file = fs.readFileSync(path).toString();
29 | this.variables = '';
30 | this.dependencies = '';
31 |
32 | if (options.variables) {
33 | this.variables = setVariables(options.variables);
34 | }
35 |
36 | if (options.dependencies) {
37 | this.dependencies = setDependencies(options.dependencies);
38 | }
39 | }
40 |
41 | Sassaby.prototype = {
42 | imports: function(name) {
43 | var message = 'Could not find an import statement with ' + name + ' in file.';
44 | assert(parsers.hasImport(this.file, name), message);
45 | },
46 |
47 | doesNotImport: function(name) {
48 | var message = 'Found an import statement with ' + name + ' in file.';
49 | assert(!parsers.hasImport(this.file, name), message);
50 | },
51 |
52 | includedMixin: function(call) {
53 | return new Mixin('included', this.variables, this.dependencies, this.file, call);
54 | },
55 |
56 | standaloneMixin: function(call) {
57 | return new Mixin('standalone', this.variables, this.dependencies, this.file, call);
58 | },
59 |
60 | func: function(call) {
61 | return new Func(this.variables, this.dependencies, this.file, call);
62 | }
63 | };
64 |
65 | if (process.env.NODE_ENV === 'test') {
66 | Sassaby.setVariables = setVariables;
67 | Sassaby.setDependencies = setDependencies;
68 | }
69 |
70 | module.exports = Sassaby;
71 |
--------------------------------------------------------------------------------
/src/types/func.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 | var FuncResult = require('../funcResult');
5 |
6 | function Func(variables, dependencies, file, call) {
7 | this.file = variables + dependencies + file;
8 | this.call = call;
9 | }
10 |
11 | Func.prototype = {
12 | called: function() {
13 | return new FuncResult(this.file, this.call);
14 | },
15 |
16 | calledWith: util.deprecate(function() {
17 | var args = Array.prototype.slice.call(arguments);
18 | return new FuncResult(this.file, this.call, args);
19 | }, 'Deprecation Warning: calledWith. Use calledWithArgs instead.'),
20 |
21 | calledWithArgs: function() {
22 | var args = Array.prototype.slice.call(arguments);
23 | return new FuncResult(this.file, this.call, args);
24 | }
25 | };
26 |
27 | module.exports = Func;
28 |
--------------------------------------------------------------------------------
/src/types/mixin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 | var MixinResult = require('../mixinResult');
5 |
6 | function Mixin(type, variables, dependencies, file, call) {
7 | this.type = type;
8 | this.file = variables + dependencies + file;
9 | this.call = call;
10 | }
11 |
12 | Mixin.prototype = {
13 | called: function() {
14 | return new MixinResult(this.type, this.file, this.call);
15 | },
16 |
17 | calledWith: util.deprecate(function() {
18 | var args = Array.prototype.slice.call(arguments);
19 | return new MixinResult(this.type, this.file, this.call, args);
20 | }, 'Deprecation Warning: calledWith. Use calledWithArgs instead.'),
21 |
22 | calledWithArgs: function() {
23 | var args = Array.prototype.slice.call(arguments);
24 | return new MixinResult(this.type, this.file, this.call, args);
25 | },
26 |
27 | calledWithBlock: function(block) {
28 | return new MixinResult(this.type, this.file, this.call, undefined, block);
29 | },
30 |
31 | calledWithBlockAndArgs: function() {
32 | var args = Array.prototype.slice.call(arguments);
33 | var block = args.shift();
34 | return new MixinResult(this.type, this.file, this.call, args, block);
35 | }
36 | };
37 |
38 | module.exports = Mixin;
39 |
--------------------------------------------------------------------------------
/src/utilities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var sass = require('node-sass');
4 | var cssmin = require('cssmin');
5 | var css = require('css');
6 |
7 | var Utilities = {
8 | compileFromString: function(string) {
9 | var s = sass.renderSync({data: string});
10 | return s.css.toString();
11 | },
12 |
13 | compileWithFile: function(file, string) {
14 | var stringWithImport = file + string;
15 | return this.compileFromString(stringWithImport);
16 | },
17 |
18 | createCss: function(file, call) {
19 | return cssmin(this.compileWithFile(file, call));
20 | },
21 |
22 | createAst: function(cssString) {
23 | return css.parse(cssString);
24 | },
25 |
26 | scrubQuotes: function(string) {
27 | return string.replace(/["']/g, "");
28 | },
29 |
30 | concatArgs: function(args) {
31 | var argString = '';
32 | args.forEach(function(arg) {
33 | argString += arg + ', ';
34 | });
35 | return argString.substring(0, argString.length - 2);
36 | }
37 | };
38 |
39 | module.exports = Utilities;
40 |
--------------------------------------------------------------------------------
/test/fixtures/ast.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "stylesheet",
3 | "stylesheet": {
4 | "rules": [
5 | {
6 | "type": "rule",
7 | "selectors": [
8 | ".test"
9 | ],
10 | "declarations": [
11 | {
12 | "type": "declaration",
13 | "property": "color",
14 | "value": "red",
15 | "position": {
16 | "start": {
17 | "line": 2,
18 | "column": 3
19 | },
20 | "end": {
21 | "line": 2,
22 | "column": 13
23 | }
24 | }
25 | }
26 | ],
27 | "position": {
28 | "start": {
29 | "line": 1,
30 | "column": 1
31 | },
32 | "end": {
33 | "line": 3,
34 | "column": 2
35 | }
36 | }
37 | },
38 | {
39 | "type": "rule",
40 | "selectors": [
41 | ".hello",
42 | ".blah"
43 | ],
44 | "declarations": [
45 | {
46 | "type": "declaration",
47 | "property": "color",
48 | "value": "blue",
49 | "position": {
50 | "start": {
51 | "line": 6,
52 | "column": 3
53 | },
54 | "end": {
55 | "line": 6,
56 | "column": 14
57 | }
58 | }
59 | },
60 | {
61 | "type": "declaration",
62 | "property": "background-color",
63 | "value": "blue",
64 | "position": {
65 | "start": {
66 | "line": 7,
67 | "column": 3
68 | },
69 | "end": {
70 | "line": 7,
71 | "column": 25
72 | }
73 | }
74 | },
75 | {
76 | "type": "declaration",
77 | "property": "font-size",
78 | "value": "16px",
79 | "position": {
80 | "start": {
81 | "line": 8,
82 | "column": 3
83 | },
84 | "end": {
85 | "line": 8,
86 | "column": 18
87 | }
88 | }
89 | }
90 | ],
91 | "position": {
92 | "start": {
93 | "line": 5,
94 | "column": 1
95 | },
96 | "end": {
97 | "line": 9,
98 | "column": 2
99 | }
100 | }
101 | }
102 | ],
103 | "parsingErrors": []
104 | }
105 | }
--------------------------------------------------------------------------------
/test/fixtures/astMediaQuery.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "stylesheet",
3 | "stylesheet": {
4 | "rules": [
5 | {
6 | "type": "media",
7 | "media": "screen and (min-width: 600px)",
8 | "rules": [
9 | {
10 | "type": "rule",
11 | "selectors": [
12 | "body"
13 | ],
14 | "declarations": [
15 | {
16 | "type": "declaration",
17 | "property": "background",
18 | "value": "green",
19 | "position": {
20 | "start": {
21 | "line": 3,
22 | "column": 5
23 | },
24 | "end": {
25 | "line": 3,
26 | "column": 22
27 | }
28 | }
29 | },
30 | {
31 | "type": "declaration",
32 | "property": "font-size",
33 | "value": "21px",
34 | "position": {
35 | "start": {
36 | "line": 4,
37 | "column": 5
38 | },
39 | "end": {
40 | "line": 4,
41 | "column": 20
42 | }
43 | }
44 | }
45 | ],
46 | "position": {
47 | "start": {
48 | "line": 2,
49 | "column": 3
50 | },
51 | "end": {
52 | "line": 5,
53 | "column": 4
54 | }
55 | }
56 | }
57 | ],
58 | "position": {
59 | "start": {
60 | "line": 1,
61 | "column": 1
62 | },
63 | "end": {
64 | "line": 6,
65 | "column": 2
66 | }
67 | }
68 | }
69 | ],
70 | "parsingErrors": []
71 | }
72 | }
--------------------------------------------------------------------------------
/test/fixtures/astMediaQueryFontFace.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "stylesheet",
3 | "stylesheet": {
4 | "rules": [
5 | {
6 | "type": "media",
7 | "media": "screen and (min-width: 600px)",
8 | "rules": [
9 | {
10 | "type": "font-face",
11 | "declarations": [
12 | {
13 | "type": "declaration",
14 | "property": "font-family",
15 | "value": "Helvetica",
16 | "position": {
17 | "start": {
18 | "line": 3,
19 | "column": 5
20 | },
21 | "end": {
22 | "line": 3,
23 | "column": 27
24 | }
25 | }
26 | },
27 | {
28 | "type": "declaration",
29 | "property": "src",
30 | "value": "url('fonts/helvetica.woff') format('woff')\n font-style: normal",
31 | "position": {
32 | "start": {
33 | "line": 4,
34 | "column": 5
35 | },
36 | "end": {
37 | "line": 5,
38 | "column": 23
39 | }
40 | }
41 | },
42 | {
43 | "type": "declaration",
44 | "property": "font-weight",
45 | "value": "200",
46 | "position": {
47 | "start": {
48 | "line": 6,
49 | "column": 5
50 | },
51 | "end": {
52 | "line": 6,
53 | "column": 21
54 | }
55 | }
56 | }
57 | ],
58 | "position": {
59 | "start": {
60 | "line": 2,
61 | "column": 3
62 | },
63 | "end": {
64 | "line": 7,
65 | "column": 4
66 | }
67 | }
68 | }
69 | ],
70 | "position": {
71 | "start": {
72 | "line": 1,
73 | "column": 1
74 | },
75 | "end": {
76 | "line": 8,
77 | "column": 2
78 | }
79 | }
80 | }
81 | ],
82 | "parsingErrors": []
83 | }
84 | }
--------------------------------------------------------------------------------
/test/fixtures/astNoSelectors.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "stylesheet",
3 | "stylesheet": {
4 | "rules": [
5 | {
6 | "type": "font-face",
7 | "declarations": [
8 | {
9 | "type": "declaration",
10 | "property": "font-family",
11 | "value": "Helvetica",
12 | "position": {
13 | "start": {
14 | "line": 2,
15 | "column": 3
16 | },
17 | "end": {
18 | "line": 2,
19 | "column": 25
20 | }
21 | }
22 | },
23 | {
24 | "type": "declaration",
25 | "property": "src",
26 | "value": "url('helvetica.woff') format('woff'),\n url('helvetica.svg') format('svg')",
27 | "position": {
28 | "start": {
29 | "line": 3,
30 | "column": 3
31 | },
32 | "end": {
33 | "line": 4,
34 | "column": 39
35 | }
36 | }
37 | },
38 | {
39 | "type": "declaration",
40 | "property": "font-style",
41 | "value": "normal",
42 | "position": {
43 | "start": {
44 | "line": 5,
45 | "column": 3
46 | },
47 | "end": {
48 | "line": 5,
49 | "column": 21
50 | }
51 | }
52 | },
53 | {
54 | "type": "declaration",
55 | "property": "font-weight",
56 | "value": "400",
57 | "position": {
58 | "start": {
59 | "line": 6,
60 | "column": 3
61 | },
62 | "end": {
63 | "line": 6,
64 | "column": 19
65 | }
66 | }
67 | }
68 | ],
69 | "position": {
70 | "start": {
71 | "line": 1,
72 | "column": 1
73 | },
74 | "end": {
75 | "line": 7,
76 | "column": 2
77 | }
78 | }
79 | }
80 | ],
81 | "parsingErrors": []
82 | }
83 | }
--------------------------------------------------------------------------------
/test/fixtures/sample-with-blocks.scss:
--------------------------------------------------------------------------------
1 | @mixin sm-specific-styles {
2 | @media only screen and (min-width: 0px) and (max-width: 400px) {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin make-small-color($color) {
8 | @media only screen and (min-width: 0px) and (max-width: 400px) {
9 | color: $color;
10 | @content;
11 | }
12 | }
13 |
14 | @mixin make-font-face {
15 | @media only screen and (min-width: 0px) and (max-width: 400px) {
16 | @font-face {
17 | @content;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/fixtures/sample-with-dependencies.scss:
--------------------------------------------------------------------------------
1 | @mixin make-button($label) {
2 | .btn-#{$label} {
3 | @include appearance(button);
4 | }
5 | }
--------------------------------------------------------------------------------
/test/fixtures/sample-with-imports.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'sample-with-variables';
3 |
4 | @import 'sample', 'sample-with-dependencies';
--------------------------------------------------------------------------------
/test/fixtures/sample-with-variables-and-dependencies.scss:
--------------------------------------------------------------------------------
1 | @mixin make-offset($label, $width) {
2 | .col-#{$label}-offset-#{$width} {
3 | margin-left: percentage($width / $grid-columns);
4 | }
5 | }
6 |
7 | @mixin make-button($label) {
8 | .btn-#{$label} {
9 | @include appearance(button);
10 | }
11 | }
--------------------------------------------------------------------------------
/test/fixtures/sample-with-variables.scss:
--------------------------------------------------------------------------------
1 | @mixin make-offset($label, $width) {
2 | .col-#{$label}-offset-#{$width} {
3 | margin-left: percentage($width / $grid-columns);
4 | }
5 | }
--------------------------------------------------------------------------------
/test/fixtures/sample.scss:
--------------------------------------------------------------------------------
1 | @mixin make-column($label, $width) {
2 | .col-#{$label}-#{$width} {
3 | flex-basis: percentage($width / 12);
4 | max-width: percentage($width / 12);
5 | }
6 | }
7 |
8 | @mixin appearance($value) {
9 | -webkit-appearance: $value;
10 | -moz-appearance: $value;
11 | appearance: $value;
12 | }
13 |
14 | @function remy($pxsize, $rembase) {
15 | @return ($pxsize/$rembase)+rem;
16 | }
17 |
18 | @function boolean-switch($var) {
19 | @if $var {
20 | @return true
21 | } @else {
22 | @return false
23 | }
24 | }
25 |
26 | @function return-self($var) {
27 | @return $var;
28 | }
29 |
30 | @mixin make-align-left($label) {
31 | .align-left-#{$label} {
32 | justify-content: flex-start;
33 | }
34 | }
35 |
36 | @mixin make-align-center($label) {
37 | .align-center-#{$label} {
38 | justify-content: center;
39 | }
40 | }
41 |
42 | @mixin make-align-right($label) {
43 | .align-right-#{$label} {
44 | justify-content: flex-end;
45 | }
46 | }
47 |
48 | @mixin make-general-alignments($label) {
49 | @include make-align-left($label);
50 | @include make-align-center($label);
51 | @include make-align-right($label);
52 | }
53 |
54 | @mixin test-included-mixin-include {
55 | .test {
56 | @include appearance(none);
57 | }
58 | }
59 |
60 | @mixin prefixer($prefix, $property, $value) {
61 | -#{$prefix}-#{$property}: $value;
62 | }
63 |
64 | @mixin animation($name, $duration) {
65 | animation-name: $name;
66 | @include prefixer(webkit, animation-name, $name);
67 | @include prefixer(moz, animation-name, $name);
68 | animation-duration: $duration;
69 | @include prefixer(webkit, animation-duration, $duration);
70 | @include prefixer(moz, animation-duration, $duration);
71 | }
72 |
73 | @mixin make-sized-button($color, $width) {
74 | @media screen and (max-width: $width) {
75 | .button {
76 | background-color: $color;
77 | }
78 | }
79 | }
80 |
81 | @mixin create-header {
82 | header {
83 | width: 100%;
84 | }
85 | }
86 |
87 | @mixin color-red {
88 | color: red;
89 | }
90 |
91 | @mixin small-screen-header {
92 | @media only screen and (min-width: 0px) and (max-width: 400px) {
93 | @include create-header;
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/test/fixtures/variables.scss:
--------------------------------------------------------------------------------
1 | $grid-columns: 12;
--------------------------------------------------------------------------------
/test/funcResultTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 | var utilities = require('../src/utilities');
7 |
8 | describe('FuncResult', function() {
9 | var FuncResult;
10 | var funcResult;
11 | var funcTrueResult;
12 | var funcFalseResult;
13 | var mockUtilities;
14 | var args = [5];
15 | var file = '@function test($input) { @return 2 * $input }';
16 | var call = 'test';
17 | var argString = '5';
18 | var result = '10';
19 | var wrappedResult = '.test{content:' + result + '}';
20 | var wrappedTrueResult = '.test{content:true}';
21 | var wrappedFalseResult = '.test{content:false}';
22 |
23 | beforeEach(function() {
24 | FuncResult = proxyquire('../src/funcResult', {
25 | './utilities': utilities
26 | });
27 |
28 | mockUtilities = sinon.mock(utilities);
29 |
30 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapFunctionWithArgs(call, args)).returns(wrappedResult);
31 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapTruthyFunctionWithArgs(call, args)).returns(wrappedTrueResult);
32 | funcResult = new FuncResult(file, call, args);
33 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapFunctionWithArgs(call, args)).returns(wrappedTrueResult);
34 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapTruthyFunctionWithArgs(call, args)).returns(wrappedTrueResult);
35 | funcTrueResult = new FuncResult(file, call, args);
36 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapFunctionWithArgs(call, args)).returns(wrappedFalseResult);
37 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapTruthyFunctionWithArgs(call, args)).returns(wrappedFalseResult);
38 | funcFalseResult = new FuncResult(file, call, args);
39 | });
40 |
41 | afterEach(function() {
42 | mockUtilities.restore();
43 | });
44 |
45 | describe('wrapFunctionWithArgs', function() {
46 | it('wraps the function and args with the necessary SCSS to not fail the compiler', function() {
47 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
48 | assert.equal(FuncResult.wrapFunctionWithArgs(call, args), '.test{content:' + call + '(' + argString + ')}');
49 | });
50 | });
51 |
52 | describe('wrapTruthyFunctionWithArgs', function() {
53 | it('wraps the function and truthy with the necessary SCSS to not fail the compiler', function() {
54 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
55 | assert.equal(FuncResult.wrapTruthyFunctionWithArgs(call, args), FuncResult.sassTruthy() + FuncResult.wrapFunction("truthy(" + call + '(' + argString + '))'));
56 | });
57 | });
58 |
59 | describe('wrapTruthyFunction', function() {
60 | it('wraps the function and truthy with the necessary SCSS to not fail the compiler', function() {
61 | assert.equal(FuncResult.wrapTruthyFunction(call), FuncResult.sassTruthy() + FuncResult.wrapFunction("truthy(" + call + ')'));
62 | });
63 | });
64 |
65 | describe('wrapFunction', function() {
66 | it('wraps the function with the necessary SCSS to not fail the compiler', function() {
67 | assert.equal(FuncResult.wrapFunction(call), '.test{content:' + call + '}');
68 | });
69 | });
70 |
71 | describe('sassTruthy', function() {
72 | it('returns the sass truthy function as a string', function() {
73 | assert.equal(FuncResult.sassTruthy(), '@function truthy($value) { @if $value { @return true } @else { @return false } }');
74 | });
75 | });
76 |
77 | describe('compileCss', function() {
78 | context('if given an argument array with items', function() {
79 | it('calls utilities.createCss with wrapFunctionWithArgs', function() {
80 | var wrapped = FuncResult.wrapFunctionWithArgs(call, args);
81 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedResult);
82 | assert.equal(FuncResult.compileCss(file, call, args), wrappedResult);
83 | });
84 | });
85 |
86 | context('if given an empty argument array', function() {
87 | it('calls utilities.createCss with wrapFunction', function() {
88 | var wrapped = FuncResult.wrapFunction(call);
89 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedResult);
90 | assert.equal(FuncResult.compileCss(file, call, []), wrappedResult);
91 | });
92 | });
93 |
94 | context('if given an null for arguments', function() {
95 | it('calls utilities.createCss with wrapFunction', function() {
96 | var wrapped = FuncResult.wrapFunction(call);
97 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedResult);
98 | assert.equal(FuncResult.compileCss(file, call, null), wrappedResult);
99 | });
100 | });
101 |
102 | context('if given an undefined for arguments', function() {
103 | it('calls utilities.createCss with wrapFunction', function() {
104 | var wrapped = FuncResult.wrapFunction(call);
105 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedResult);
106 | assert.equal(FuncResult.compileCss(file, call, undefined), wrappedResult);
107 | });
108 | });
109 | });
110 |
111 | describe('compileTruthyCss', function() {
112 | context('if given an argument array with items', function() {
113 | it('calls utilities.createCss with wrapFunctionWithArgs', function() {
114 | var wrapped = FuncResult.wrapTruthyFunctionWithArgs(call, args);
115 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedTrueResult);
116 | assert.equal(FuncResult.compileTruthyCss(file, call, args), wrappedTrueResult);
117 | });
118 | });
119 |
120 | context('if given an empty argument array', function() {
121 | it('calls utilities.createCss with wrapFunction', function() {
122 | var wrapped = FuncResult.wrapTruthyFunction(call);
123 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedTrueResult);
124 | assert.equal(FuncResult.compileTruthyCss(file, call, []), wrappedTrueResult);
125 | });
126 | });
127 |
128 | context('if given an null for arguments', function() {
129 | it('calls utilities.createCss with wrapFunction', function() {
130 | var wrapped = FuncResult.wrapTruthyFunction(call);
131 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedTrueResult);
132 | assert.equal(FuncResult.compileTruthyCss(file, call, null), wrappedTrueResult);
133 | });
134 | });
135 |
136 | context('if given an undefined for arguments', function() {
137 | it('calls utilities.createCss with wrapFunction', function() {
138 | var wrapped = FuncResult.wrapTruthyFunction(call);
139 | mockUtilities.expects('createCss').withArgs(file, wrapped).returns(wrappedTrueResult);
140 | assert.equal(FuncResult.compileTruthyCss(file, call, undefined), wrappedTrueResult);
141 | });
142 | });
143 | });
144 |
145 | describe('new', function() {
146 | beforeEach(function() {
147 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapFunctionWithArgs(call, args)).returns(wrappedResult);
148 | mockUtilities.expects('createCss').withArgs(file, FuncResult.wrapTruthyFunctionWithArgs(call, args)).returns(wrappedTrueResult);
149 | sinon.spy(FuncResult, 'compileCss');
150 | sinon.spy(FuncResult, 'compileTruthyCss');
151 | funcResult = new FuncResult(file, call, args);
152 | FuncResult.compileCss.calledWith(file, call, args);
153 | FuncResult.compileTruthyCss.calledWith(file, call, args);
154 | });
155 |
156 | afterEach(function() {
157 | FuncResult.compileCss.restore();
158 | FuncResult.compileTruthyCss.restore();
159 | });
160 |
161 | it('should set file and call from the arguments', function() {
162 | assert.equal(funcResult.css, wrappedResult);
163 | assert.equal(funcResult.truthyCss, wrappedTrueResult);
164 | });
165 | });
166 |
167 | describe('equals', function() {
168 | it('should not throw an error if the output matches the input', function() {
169 | funcResult.equals(result);
170 | });
171 |
172 | it('throws an error if the output does not match the input', function() {
173 | assert.throws(function() { funcResult.equals(20); });
174 | });
175 | });
176 |
177 | describe('doesNotEqual', function() {
178 | it('should not throw an error if the output does not match the input', function() {
179 | funcResult.doesNotEqual(20);
180 | });
181 |
182 | it('throws an error if the output does input', function() {
183 | assert.throws(function() {
184 | funcResult.doesNotEqual(result);
185 | });
186 | });
187 | });
188 |
189 | describe('isTrue', function() {
190 | it('should not throw an error if the output is true', function() {
191 | funcTrueResult.isTrue();
192 | });
193 |
194 | it('throws an error if the output is not true', function() {
195 | assert.throws(function() {
196 | funcFalseResult.isTrue();
197 | });
198 | });
199 | });
200 |
201 | describe('isFalse', function() {
202 | it('should not throw an error if the output is false', function() {
203 | funcFalseResult.isFalse();
204 | });
205 |
206 | it('throws an error if the output is not false', function() {
207 | assert.throws(function() {
208 | funcTrueResult.isFalse();
209 | });
210 | });
211 | });
212 |
213 | describe('isTruthy', function() {
214 | it('should not throw an error if the output is truthy', function() {
215 | funcTrueResult.isTruthy();
216 | });
217 |
218 | it('throws an error if the output is not truthy', function() {
219 | assert.throws(function() {
220 | funcFalseResult.isTruthy();
221 | });
222 | });
223 | });
224 |
225 | describe('isFalsy', function() {
226 | it('should not throw an error if the output is falsy', function() {
227 | funcFalseResult.isFalsy();
228 | });
229 |
230 | it('throws an error if the output is not falsy', function() {
231 | assert.throws(function() {
232 | funcTrueResult.isFalsy();
233 | });
234 | });
235 | });
236 | });
237 |
--------------------------------------------------------------------------------
/test/funcTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 |
7 | function MockFuncResult(file, call, args) {
8 | this.file = file;
9 | this.call = call;
10 | this.args = args;
11 | }
12 |
13 | describe('Func', function() {
14 | var Func;
15 | var func;
16 |
17 | var variables = '$test:5;';
18 | var dependencies = "@import 'file';";
19 | var file = '@function test($input) { @return 2 * $input }';
20 | var call = 'test(5)';
21 |
22 | beforeEach(function() {
23 | Func = proxyquire('../src/types/func', {
24 | '../funcResult': MockFuncResult
25 | });
26 | func = new Func(variables, dependencies, file, call);
27 | });
28 |
29 | describe('new', function() {
30 | it('should set file and call from the arguments', function() {
31 | assert.equal(func.file, variables + dependencies + file);
32 | assert.equal(func.call, call);
33 | });
34 | });
35 |
36 | describe('called', function() {
37 | it('should return a new funcResult', function() {
38 | var result = func.called();
39 | assert(result instanceof MockFuncResult);
40 | });
41 |
42 | it('should have the correct properties', function() {
43 | var result = func.called();
44 | assert.equal(result.file, variables + dependencies + file);
45 | assert.equal(result.call, call);
46 | assert.equal(result.args, undefined);
47 | });
48 | });
49 |
50 | describe('calledWith', function() {
51 | it('should return a new funcResult', function() {
52 | var result = func.calledWith(1, 2, 'hello', true);
53 | assert(result instanceof MockFuncResult);
54 | });
55 |
56 | it('should have the correct properties', function() {
57 | var result = func.calledWith(1, 2, 'hello', true);
58 | assert.equal(result.file, variables + dependencies + file);
59 | assert.equal(result.call, call);
60 | assert.deepEqual(result.args, [1, 2, 'hello', true]);
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/integrationTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample.scss'));
8 | var mixin;
9 | var compiled;
10 |
11 | describe('appearance', function() {
12 | beforeEach(function() {
13 | mixin = sassaby.includedMixin('appearance');
14 | });
15 |
16 | it('should return 3 declarations', function() {
17 | mixin.calledWithArgs('button').hasNumDeclarations(3);
18 | });
19 |
20 | it('should have a webkit prefixed declaration', function() {
21 | mixin.calledWithArgs('button').declares('-webkit-appearance', 'button');
22 | });
23 |
24 | it('should not make an incorrect declaration', function() {
25 | mixin.calledWithArgs('button').doesNotDeclare('-webkit-appearance', 'none');
26 | mixin.calledWithArgs('button').doesNotDeclare('color', 'red');
27 | });
28 |
29 | it('should have the correct entire output', function() {
30 | mixin.calledWithArgs('button').equals('-webkit-appearance: button; -moz-appearance: button; appearance: button;');
31 | });
32 |
33 | it('should not equal the incorrect output', function() {
34 | mixin.calledWithArgs('button').doesNotEqual('-moz-appearance: button; appearance: button;');
35 | });
36 | });
37 |
38 | describe('make-column', function() {
39 | beforeEach(function() {
40 | mixin = sassaby.standaloneMixin('make-column');
41 | });
42 |
43 | it('should define the correct class', function() {
44 | mixin.calledWithArgs('md', 6).createsSelector('.col-md-6');
45 | });
46 |
47 | it('should not define the incorrect class', function() {
48 | mixin.calledWithArgs('md', 6).doesNotCreateSelector('.col-lg-6');
49 | });
50 |
51 | it('should return 2 declarations', function() {
52 | mixin.calledWithArgs('md', 6).hasNumDeclarations(2);
53 | });
54 |
55 | it('should declare the correct width', function() {
56 | mixin.calledWithArgs('md', 6).declares('max-width', '50%');
57 | });
58 |
59 | it('should not declare the incorrect width', function() {
60 | mixin.calledWithArgs('md', 6).doesNotDeclare('max-width', '60%');
61 | mixin.calledWithArgs('md', 6).doesNotDeclare('appearance', 'button');
62 | });
63 |
64 | it('should have the correct entire output', function() {
65 | mixin.calledWithArgs('md', 6).equals('.col-md-6 { flex-basis: 50%; max-width: 50%; }');
66 | });
67 |
68 | it('should not have incorrect output', function() {
69 | mixin.calledWithArgs('md', 6).doesNotEqual('.col-md-8 { flex-basis: 50%; max-width: 50%; }');
70 | });
71 | });
72 |
73 | describe('make-sized-button', function() {
74 | beforeEach(function() {
75 | mixin = sassaby.standaloneMixin('make-sized-button');
76 | compiled = mixin.calledWithArgs('red', '200px');
77 | });
78 |
79 | it('should create the correct media query', function() {
80 | compiled.createsMediaQuery('screen and (max-width: 200px)');
81 | });
82 |
83 | it('should not create the incorrect media query', function() {
84 | compiled.doesNotCreateMediaQuery('screen and (max-width: 400px)');
85 | });
86 |
87 | it('should create the button class', function() {
88 | compiled.createsSelector('.button');
89 | });
90 |
91 | it('should declare the correct background-color', function() {
92 | compiled.declares('background-color', 'red');
93 | });
94 | });
95 |
96 | describe('remy', function() {
97 | it('convert to px units to rem units', function() {
98 | sassaby.func('remy').calledWithArgs('32px', '16px').equals('2rem');
99 | });
100 |
101 | it('has the correct output unit', function() {
102 | sassaby.func('remy').calledWithArgs('32px', '16px').doesNotEqual('2em');
103 | });
104 | });
105 |
106 | describe('boolean-switch', function() {
107 | it('should return true if passed true', function() {
108 | sassaby.func('boolean-switch').calledWithArgs(true).isTrue();
109 | });
110 |
111 | it('should return false if passed false', function() {
112 | sassaby.func('boolean-switch').calledWithArgs(false).isFalse();
113 | });
114 | });
115 |
116 | describe('return-self', function() {
117 | it('testing truthy', function() {
118 | sassaby.func('return-self').calledWithArgs(true).isTruthy();
119 | sassaby.func('return-self').calledWithArgs(1).isTruthy();
120 | sassaby.func('return-self').calledWithArgs('a').isTruthy();
121 | });
122 |
123 | it('testing falsy', function() {
124 | sassaby.func('return-self').calledWithArgs(false).isFalsy();
125 | sassaby.func('return-self').calledWithArgs(null).isFalsy();
126 | });
127 | });
128 |
129 | describe('make-general-alignments', function() {
130 | beforeEach(function() {
131 | mixin = sassaby.standaloneMixin('make-general-alignments');
132 | });
133 |
134 | it('should call the correct mixins', function() {
135 | mixin.calledWithArgs('md').calls('make-align-left(md)');
136 | mixin.calledWithArgs('md').calls('make-align-center(md)');
137 | mixin.calledWithArgs('md').calls('make-align-right(md)');
138 | });
139 |
140 | it('should not call the incorrect mixin', function() {
141 | mixin.calledWithArgs('md').doesNotCall('make-align-left(lg)');
142 | });
143 | });
144 |
145 | describe('test-included-mixin-include', function() {
146 | beforeEach(function() {
147 | mixin = sassaby.standaloneMixin('test-included-mixin-include');
148 | compiled = mixin.called();
149 | });
150 |
151 | it('should create the correct selector', function() {
152 | compiled.createsSelector('.test');
153 | });
154 |
155 | it('should call the correct mixin', function() {
156 | compiled.calls('appearance(none)');
157 | });
158 | });
159 |
160 | describe('animation', function() {
161 | beforeEach(function() {
162 | mixin = sassaby.includedMixin('animation');
163 | });
164 |
165 | it('should have the correct output', function() {
166 | mixin.calledWithArgs('test', 500).declares('animation-name', 'test');
167 | mixin.calledWithArgs('test', 500).declares('animation-duration', 500);
168 | });
169 |
170 | it('should call the correct mixins', function() {
171 | mixin.calledWithArgs('test', 500).calls('prefixer(webkit, animation-name, test)');
172 | mixin.calledWithArgs('test', 500).calls('prefixer(moz, animation-name, test)');
173 | mixin.calledWithArgs('test', 500).calls('prefixer(webkit, animation-duration, 500)');
174 | mixin.calledWithArgs('test', 500).calls('prefixer(moz, animation-duration, 500)');
175 | });
176 |
177 | it('should not call the incorrect mixins', function() {
178 | mixin.calledWithArgs('test', 500).doesNotCall('prefixer(o, animation-name, test)');
179 | });
180 | });
181 |
182 | describe('create-header', function() {
183 | beforeEach(function() {
184 | mixin = sassaby.standaloneMixin('create-header');
185 | });
186 |
187 | it('should have the correct output', function() {
188 | mixin.called().declares('width', '100%');
189 | });
190 |
191 | it('should create a header selector', function() {
192 | mixin.called().createsSelector('header');
193 | });
194 | });
195 |
196 | describe('color-red', function() {
197 | beforeEach(function() {
198 | mixin = sassaby.includedMixin('color-red');
199 | });
200 |
201 | it('should have the correct output', function() {
202 | mixin.called().declares('color', 'red');
203 | });
204 | });
205 |
206 | describe('small-screen-header', function() {
207 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 400px)';
208 |
209 | beforeEach(function() {
210 | mixin = sassaby.standaloneMixin('small-screen-header');
211 | });
212 |
213 | it('should create a media query', function() {
214 | mixin.called().createsMediaQuery(mediaQuery);
215 | });
216 |
217 | it('should call create-header', function() {
218 | mixin.called().calls('create-header');
219 | });
220 | });
221 | });
222 |
--------------------------------------------------------------------------------
/test/integrationTestsWithBlocks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample-with-blocks.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample-with-blocks.scss'), {
8 | dependencies: [
9 | path.resolve(__dirname, 'fixtures/sample.scss')
10 | ]
11 | });
12 |
13 | describe('sm-specific-styles', function() {
14 | var block = '.test {color: red;}';
15 | var mixin;
16 | var call;
17 |
18 | beforeEach(function() {
19 | mixin = sassaby.standaloneMixin('sm-specific-styles');
20 | call = mixin.calledWithBlock(block);
21 | });
22 |
23 | it('should create the correct media query', function() {
24 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 400px)';
25 | call.createsMediaQuery(mediaQuery);
26 | });
27 |
28 | it('should not create the correct media query', function() {
29 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 600px)';
30 | call.doesNotCreateMediaQuery(mediaQuery);
31 | });
32 |
33 | it('should create the correct class', function() {
34 | call.createsSelector('.test');
35 | });
36 |
37 | it('should not create the correct class', function() {
38 | call.doesNotCreateSelector('.blah');
39 | });
40 |
41 | it('should have the correct number of declarations', function() {
42 | call.hasNumDeclarations(1);
43 | });
44 |
45 | it('should have the correct color declaration', function() {
46 | call.declares('color', 'red');
47 | });
48 |
49 | it('should have the correct total output', function() {
50 | var output = '@media only screen and (min-width: 0px) and (max-width: 400px) {' + block + '}';
51 | call.equals(output);
52 | });
53 |
54 | it('should call create-header', function() {
55 | var block = '@include create-header';
56 | mixin.calledWithBlock(block).calls('create-header');
57 | });
58 | });
59 |
60 | describe('make-font-face', function() {
61 | var block = 'font-family: Helvetica';
62 | var mixin;
63 | var call;
64 |
65 | beforeEach(function() {
66 | mixin = sassaby.standaloneMixin('make-font-face');
67 | call = mixin.calledWithBlock(block);
68 | });
69 |
70 | it('should create the correct media query', function() {
71 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 400px)';
72 | call.createsMediaQuery(mediaQuery);
73 | });
74 |
75 | it('should not create the correct media query', function() {
76 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 600px)';
77 | call.doesNotCreateMediaQuery(mediaQuery);
78 | });
79 |
80 | it('should create the correct class', function() {
81 | call.createsFontFace();
82 | });
83 |
84 | it('should have the correct font-family declaration', function() {
85 | call.declares('font-family', 'Helvetica');
86 | });
87 | });
88 |
89 | describe('sm-specific-styles', function() {
90 | var block = 'color: red;';
91 | var mixin;
92 |
93 | beforeEach(function() {
94 | mixin = sassaby.includedMixin('sm-specific-styles');
95 | });
96 |
97 | it('should create the correct media query', function() {
98 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 400px)';
99 | mixin.calledWithBlock(block).createsMediaQuery(mediaQuery);
100 | });
101 |
102 | it('should have the correct color declaration', function() {
103 | mixin.calledWithBlock(block).declares('color', 'red');
104 | });
105 | });
106 |
107 | describe('make-small-color', function() {
108 | var block = 'width: 100%;';
109 | var color = 'red';
110 | var mixin;
111 |
112 | beforeEach(function() {
113 | mixin = sassaby.includedMixin('make-small-color');
114 | });
115 |
116 | it('should create the correct media query', function() {
117 | var mediaQuery = 'only screen and (min-width: 0px) and (max-width: 400px)';
118 | mixin.calledWithBlockAndArgs(block, color).createsMediaQuery(mediaQuery);
119 | });
120 |
121 | it('should have the correct color declaration', function() {
122 | mixin.calledWithBlockAndArgs(block, color).declares('color', 'red');
123 | });
124 |
125 | it('should have the correct width declaration', function() {
126 | mixin.calledWithBlockAndArgs(block, color).declares('width', '100%');
127 | });
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/test/integrationTestsWithDependencies.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample-with-dependencies.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample-with-dependencies.scss'), {
8 | dependencies: [
9 | path.resolve(__dirname, 'fixtures/sample.scss')
10 | ]
11 | });
12 |
13 | describe('make-button', function() {
14 | var mixin;
15 |
16 | beforeEach(function() {
17 | mixin = sassaby.standaloneMixin('make-button');
18 | });
19 |
20 | it('should return 3 declarations', function() {
21 | mixin.calledWithArgs('md').hasNumDeclarations(3);
22 | });
23 |
24 | it('should create the correct class', function() {
25 | mixin.calledWithArgs('md').createsSelector('.btn-md');
26 | });
27 |
28 | it('should have a webkit prefixed declaration', function() {
29 | mixin.calledWithArgs('md').declares('-webkit-appearance', 'button');
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/integrationTestsWithImports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample-with-imports.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample-with-imports.scss'));
8 |
9 | describe('imports', function() {
10 | it('should import variables', function() {
11 | sassaby.imports('variables');
12 | });
13 |
14 | it('should import sample-with-variables', function() {
15 | sassaby.imports('sample-with-variables');
16 | });
17 |
18 | it('should import sample', function() {
19 | sassaby.imports('sample');
20 | });
21 |
22 | it('should import sample-with-dependencies', function() {
23 | sassaby.imports('sample-with-dependencies');
24 | });
25 |
26 | it('should not import nope', function() {
27 | sassaby.doesNotImport('nope');
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/test/integrationTestsWithVariables.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample-with-variables.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample-with-variables.scss'), {
8 | variables: {
9 | 'grid-columns': 12
10 | }
11 | });
12 |
13 | describe('make-offset', function() {
14 | var mixin;
15 |
16 | beforeEach(function() {
17 | mixin = sassaby.standaloneMixin('make-offset');
18 | });
19 |
20 | it('should return 1 declarations', function() {
21 | mixin.calledWithArgs('md', 6).hasNumDeclarations(1);
22 | });
23 |
24 | it('should create the correct class', function() {
25 | mixin.calledWithArgs('md', 6).createsSelector('.col-md-offset-6');
26 | });
27 |
28 | it('should have a webkit prefixed declaration', function() {
29 | mixin.calledWithArgs('md', 6).declares('margin-left', '50%');
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/integrationTestsWithVariablesAndDependencies.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var Sassaby = require('../src/sassaby');
5 |
6 | describe('sample-with-variables.scss', function() {
7 | var sassaby = new Sassaby(path.resolve(__dirname, 'fixtures/sample-with-variables-and-dependencies.scss'), {
8 | variables: {
9 | 'grid-columns': 12
10 | },
11 | dependencies: [
12 | path.resolve(__dirname, 'fixtures/sample.scss')
13 | ]
14 | });
15 |
16 | describe('make-offset', function() {
17 | var mixin;
18 |
19 | beforeEach(function() {
20 | mixin = sassaby.standaloneMixin('make-offset');
21 | });
22 |
23 | it('should return 1 declarations', function() {
24 | mixin.calledWithArgs('md', 6).hasNumDeclarations(1);
25 | });
26 |
27 | it('should create the correct class', function() {
28 | mixin.calledWithArgs('md', 6).createsSelector('.col-md-offset-6');
29 | });
30 |
31 | it('should have a webkit prefixed declaration', function() {
32 | mixin.calledWithArgs('md', 6).declares('margin-left', '50%');
33 | });
34 | });
35 |
36 | describe('make-button', function() {
37 | var mixin;
38 |
39 | beforeEach(function() {
40 | mixin = sassaby.standaloneMixin('make-button');
41 | });
42 |
43 | it('should return 3 declarations', function() {
44 | mixin.calledWithArgs('md').hasNumDeclarations(3);
45 | });
46 |
47 | it('should create the correct class', function() {
48 | mixin.calledWithArgs('md').createsSelector('.btn-md');
49 | });
50 |
51 | it('should have a webkit prefixed declaration', function() {
52 | mixin.calledWithArgs('md').declares('-webkit-appearance', 'button');
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/test/mixinResultTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 | var utilities = require('../src/utilities');
7 | var parsers = require('../src/parsers');
8 | var ast = require('./fixtures/ast.json');
9 |
10 |
11 | describe('MixinResult', function() {
12 | var MixinResult;
13 | var cssmin;
14 | var mockUtilities;
15 | var mockParsers;
16 | var standaloneMixinResult;
17 | var standaloneMixinResultWithBlock;
18 | var standaloneMixinResultWithBlockAndArgs;
19 | var includedMixinResult;
20 | var includedMixinResultWithBlock;
21 | var includedMixinResultWithBlockAndArgs;
22 |
23 | var includedFile = '@mixin test($input) { color: $input }';
24 | var standaloneFile = '@mixin test($input) { .test { color: $input; } }';
25 | var call = '@include test(red)';
26 | var selector = '.test';
27 | var property = 'color';
28 | var value = 'red';
29 | var result = property + ':' + value;
30 | var resultWithSemicolon = property + ':' + value + ';';
31 | var wrappedResult = selector + '{' + result + '}';
32 | var wrappedResultWithSemicolon = selector + '{' + resultWithSemicolon + '}';
33 |
34 | var args = [1, 2, 'hello', true];
35 | var argString = '1, 2, hello, true';
36 |
37 | beforeEach(function() {
38 | cssmin = sinon.spy(function(input) { return input; });
39 | MixinResult = proxyquire('../src/mixinResult', {
40 | './utilities': utilities,
41 | './parsers': parsers,
42 | 'cssmin': cssmin
43 | });
44 |
45 | mockUtilities = sinon.mock(utilities);
46 | mockParsers = sinon.mock(parsers);
47 |
48 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixinWithArgs('standalone', call, args)).returns(wrappedResult);
49 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
50 | standaloneMixinResult = new MixinResult('standalone', standaloneFile, call, args);
51 |
52 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixinWithBlock('standalone', call, wrappedResult)).returns(wrappedResult);
53 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
54 | standaloneMixinResultWithBlock = new MixinResult('standalone', standaloneFile, call, [], wrappedResult);
55 |
56 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixinWithBlockAndArgs('standalone', call, wrappedResult, args)).returns(wrappedResult);
57 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
58 | standaloneMixinResultWithBlockAndArgs = new MixinResult('standalone', standaloneFile, call, args, wrappedResult);
59 |
60 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixinWithArgs('included', call, args)).returns(wrappedResult);
61 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
62 | includedMixinResult = new MixinResult('included', includedFile, call, args);
63 |
64 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixinWithBlock('included', call, result)).returns(wrappedResult);
65 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
66 | includedMixinResultWithBlock = new MixinResult('included', includedFile, call, [], result);
67 |
68 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixinWithBlockAndArgs('included', call, result, args)).returns(wrappedResult);
69 | mockUtilities.expects('createAst').withArgs(wrappedResult).returns(ast);
70 | includedMixinResultWithBlockAndArgs = new MixinResult('included', includedFile, call, args, result);
71 | });
72 |
73 | afterEach(function() {
74 | mockUtilities.restore();
75 | mockParsers.restore();
76 | });
77 |
78 | describe('wrapMixinWithArgs', function() {
79 | it('wraps the standalone mixin to not fail the compiler', function() {
80 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
81 | assert.equal(MixinResult.wrapMixinWithArgs('standalone', call, args), '@include ' + call + '(' + argString + ');');
82 | });
83 |
84 | it('wraps the included mixin to not fail the compiler', function() {
85 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
86 | assert.equal(MixinResult.wrapMixinWithArgs('included', call, args), '.test{@include ' + call + '(' + argString + ');}');
87 | });
88 | });
89 |
90 | describe('wrapMixinWithBlock', function() {
91 | it('wraps the standalone mixin to not fail the compiler', function() {
92 | assert.equal(MixinResult.wrapMixinWithBlock('standalone', call, result), '@include ' + call + '{' + result + '}');
93 | });
94 |
95 | it('wraps the included mixin to not fail the compiler', function() {
96 | assert.equal(MixinResult.wrapMixinWithBlock('included', call, result), '.test{@include ' + call + '{' + result + '}}');
97 | });
98 | });
99 |
100 | describe('wrapMixinWithBlockAndArgs', function() {
101 | it('wraps the standalone mixin to not fail the compiler', function() {
102 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
103 | assert.equal(MixinResult.wrapMixinWithBlockAndArgs('standalone', call, result, args), '@include ' + call + '(' + argString + ') ' + '{' + result + '}');
104 | });
105 |
106 | it('wraps the included mixin to not fail the compiler', function() {
107 | mockUtilities.expects('concatArgs').withArgs(args).returns(argString);
108 | assert.equal(MixinResult.wrapMixinWithBlockAndArgs('included', call, result, args), '.test{@include ' + call + '(' + argString + ') ' + '{' + result + '}}');
109 | });
110 | });
111 |
112 | describe('wrapMixin', function() {
113 | it('wraps the standalone mixin to not fail the compiler', function() {
114 | assert.equal(MixinResult.wrapMixin('standalone', call), '@include ' + call + ';');
115 | });
116 |
117 | it('wraps the included mixin to not fail the compiler', function() {
118 | assert.equal(MixinResult.wrapMixin('included', call), '.test{@include ' + call + ';}');
119 | });
120 | });
121 |
122 | describe('wrapOutput', function() {
123 | it('wraps the standalone output', function() {
124 | assert.equal(MixinResult.wrapOutput('standalone', wrappedResult), wrappedResult);
125 | assert(cssmin.calledWith(wrappedResult));
126 | });
127 |
128 | it('wraps the included output', function() {
129 | assert.equal(MixinResult.wrapOutput('included', result), wrappedResult);
130 | assert(cssmin.calledWith(wrappedResult));
131 | });
132 | });
133 |
134 | describe('unwrapOutput', function() {
135 | it('unwraps the standalone output', function() {
136 | assert.equal(MixinResult.unwrapOutput('standalone', wrappedResult), wrappedResult);
137 | });
138 |
139 | it('unwraps the included output', function() {
140 | assert.equal(MixinResult.unwrapOutput('included', wrappedResultWithSemicolon), result);
141 | });
142 | });
143 |
144 | describe('compileCss', function() {
145 | context('if given an argument array with items and a block', function() {
146 | it('calls utilities.createCss with wrapMixinWithBlockAndArgs', function() {
147 | var wrapped = MixinResult.wrapMixinWithBlockAndArgs('standalone', call, result, args);
148 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
149 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, args, result), wrappedResult);
150 | });
151 | });
152 |
153 | context('if given an argument array with items and no block', function() {
154 | it('calls utilities.createCss with wrapMixinWithArgs', function() {
155 | var wrapped = MixinResult.wrapMixinWithArgs('standalone', call, args);
156 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
157 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, args), wrappedResult);
158 | });
159 | });
160 |
161 | context('if given an empty argument array and a block', function() {
162 | it('calls utilities.createCss with wrapMixinWithBlock', function() {
163 | var wrapped = MixinResult.wrapMixinWithBlock('standalone', call, result);
164 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
165 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, [], result), wrappedResult);
166 | });
167 | });
168 |
169 | context('if given null for arguments and a block', function() {
170 | it('calls utilities.createCss with wrapMixinWithBlock', function() {
171 | var wrapped = MixinResult.wrapMixinWithBlock('standalone', call, result);
172 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
173 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, null, result), wrappedResult);
174 | });
175 | });
176 |
177 | context('if given undefined for arguments and a block', function() {
178 | it('calls utilities.createCss with wrapMixinWithBlock', function() {
179 | var wrapped = MixinResult.wrapMixinWithBlock('standalone', call, result);
180 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
181 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, undefined, result), wrappedResult);
182 | });
183 | });
184 |
185 | context('if given an empty argument array and no block', function() {
186 | it('returns an empty string', function() {
187 | var wrapped = MixinResult.wrapMixin('standalone', call);
188 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
189 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, [], undefined), wrappedResult);
190 | });
191 | });
192 |
193 | context('if given undefined for args and the block', function() {
194 | it('returns an empty string', function() {
195 | var wrapped = MixinResult.wrapMixin('standalone', call);
196 | mockUtilities.expects('createCss').withArgs(standaloneFile, wrapped).returns(wrappedResult);
197 | assert.equal(MixinResult.compileCss(standaloneFile, 'standalone', call, undefined, undefined), wrappedResult);
198 | });
199 | });
200 | });
201 |
202 | describe('new', function() {
203 | it('should set type, file, css, and ast for standalone mixin', function() {
204 | assert.equal(standaloneMixinResult.type, 'standalone');
205 | assert.equal(standaloneMixinResult.file, standaloneFile);
206 | assert.equal(standaloneMixinResult.css, wrappedResult);
207 | assert.equal(standaloneMixinResult.ast, ast);
208 | });
209 |
210 | it('should set type, file, css, and ast for included mixin', function() {
211 | assert.equal(includedMixinResult.type, 'included');
212 | assert.equal(includedMixinResult.file, includedFile);
213 | assert.equal(includedMixinResult.css, wrappedResult);
214 | assert.equal(includedMixinResult.ast, ast);
215 | });
216 | });
217 |
218 | describe('createsSelector', function() {
219 | it('should not throw an error if the selector is created by the standalone mixin', function() {
220 | mockParsers.expects('hasSelector').withArgs(ast, selector).returns(true);
221 | standaloneMixinResult.createsSelector(selector);
222 | });
223 |
224 | it('throws an error if the selector is not created by the standalone mixin', function() {
225 | mockParsers.expects('hasSelector').withArgs(ast, selector).returns(false);
226 | assert.throws(function() { standaloneMixinResult.createsSelector(selector); });
227 | });
228 |
229 | it('throws an error if the function is called on an included mixin', function() {
230 | assert.throws(function() { includedMixinResult.createsSelector(selector); });
231 | });
232 | });
233 |
234 | describe('doesNotCreateSelector', function() {
235 | it('should not throw an error if the selector is not created by the standalone mixin', function() {
236 | mockParsers.expects('hasSelector').withArgs(ast, selector).returns(false);
237 | standaloneMixinResult.doesNotCreateSelector(selector);
238 | });
239 |
240 | it('throws an error if the selector is created by the standalone mixin', function() {
241 | mockParsers.expects('hasSelector').withArgs(ast, selector).returns(true);
242 | assert.throws(function() { standaloneMixinResult.doesNotCreateSelector(selector); });
243 | });
244 |
245 | it('throws an error if the function is called on an included mixin', function() {
246 | assert.throws(function() { includedMixinResult.doesNotCreateSelector(selector); });
247 | });
248 | });
249 |
250 | describe('createsMediaQuery', function() {
251 | var mediaQuery = 'screen and (min-width: 600px)';
252 | var rule = {
253 | type: 'media',
254 | media: mediaQuery
255 | };
256 |
257 | it('should not throw an error if the media query is created by the standalone mixin', function() {
258 | mockParsers.expects('findMedia').withArgs(ast).returns(rule);
259 | standaloneMixinResult.createsMediaQuery(mediaQuery);
260 | assert(cssmin.calledWith(mediaQuery));
261 | });
262 |
263 | it('throws an error if the media query is not created by the standalone mixin', function() {
264 | mockParsers.expects('findMedia').withArgs(ast).returns(undefined);
265 | assert.throws(function() { standaloneMixinResult.createsMediaQuery(mediaQuery); });
266 | assert(cssmin.calledWith(mediaQuery));
267 | });
268 | });
269 |
270 | describe('doesNotCreateMediaQuery', function() {
271 | var mediaQuery = 'screen and (min-width: 600px)';
272 | var rule = {
273 | type: 'media',
274 | media: mediaQuery
275 | };
276 |
277 | it('should not throw an error if the media query is not created by the standalone mixin', function() {
278 | mockParsers.expects('findMedia').withArgs(ast).returns(undefined);
279 | standaloneMixinResult.doesNotCreateMediaQuery(mediaQuery);
280 | assert(cssmin.calledWith(mediaQuery));
281 | });
282 |
283 | it('throws an error if the media query is created by the standalone mixin', function() {
284 | mockParsers.expects('findMedia').withArgs(ast).returns(rule);
285 | assert.throws(function() { standaloneMixinResult.doesNotCreateMediaQuery(mediaQuery); });
286 | assert(cssmin.calledWith(mediaQuery));
287 | });
288 | });
289 |
290 | describe('createsFontFace', function() {
291 | it('should not throw an error if the selector is created by the standalone mixin', function() {
292 | mockParsers.expects('hasFontFace').withArgs(ast).returns(true);
293 | standaloneMixinResult.createsFontFace();
294 | });
295 |
296 | it('throws an error if the selector is not created by the standalone mixin', function() {
297 | mockParsers.expects('hasFontFace').withArgs(ast).returns(false);
298 | assert.throws(function() { standaloneMixinResult.createsFontFace(); });
299 | });
300 |
301 | it('throws an error if the function is called on an included mixin', function() {
302 | assert.throws(function() { includedMixinResult.createsFontFace(); });
303 | });
304 | });
305 |
306 | describe('doesNotCreateFontFace', function() {
307 | it('should not throw an error if the selector is created by the standalone mixin', function() {
308 | mockParsers.expects('hasFontFace').withArgs(ast).returns(false);
309 | standaloneMixinResult.doesNotCreateFontFace();
310 | });
311 |
312 | it('throws an error if the selector is not created by the standalone mixin', function() {
313 | mockParsers.expects('hasFontFace').withArgs(ast).returns(true);
314 | assert.throws(function() { standaloneMixinResult.doesNotCreateFontFace(); });
315 | });
316 |
317 | it('throws an error if the function is called on an included mixin', function() {
318 | assert.throws(function() { includedMixinResult.doesNotCreateFontFace(); });
319 | });
320 | });
321 |
322 | describe('hasNumDeclarations', function() {
323 | context('for a standalone mixin', function() {
324 | it('should not throw an error if mixin creates the given amount of declarations', function() {
325 | mockParsers.expects('countDeclarations').withArgs(ast).returns(4);
326 | standaloneMixinResult.hasNumDeclarations(4);
327 | });
328 |
329 | it('should throw an error if mixin does not create the given amount of declarations', function() {
330 | mockParsers.expects('countDeclarations').withArgs(ast).returns(4);
331 | assert.throws(function() { standaloneMixinResult.hasNumDeclarations(5); });
332 | });
333 | });
334 |
335 | context('for an included mixin', function() {
336 | it('should not throw an error if mixin creates the given amount of declarations', function() {
337 | mockParsers.expects('countDeclarations').withArgs(ast).returns(4);
338 | includedMixinResult.hasNumDeclarations(4);
339 | });
340 |
341 | it('should throw an error if mixin does not create the given amount of declarations', function() {
342 | mockParsers.expects('countDeclarations').withArgs(ast).returns(4);
343 | assert.throws(function() { includedMixinResult.hasNumDeclarations(5); });
344 | });
345 | });
346 | });
347 |
348 | describe('declares', function() {
349 | context('for a standalone mixin', function() {
350 | it('should not throw an error if mixin creates a declaration with the given property and value', function() {
351 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
352 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
353 | standaloneMixinResult.declares(property, value);
354 | });
355 |
356 | it('should throw an error if mixin creates a declaration with the given property and not the value', function() {
357 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
358 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
359 | assert.throws(function() { standaloneMixinResult.declares(property, 'blue'); });
360 | });
361 |
362 | it('should throw an error if the given property cannot be found in mixin output', function() {
363 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns(undefined);
364 | assert.throws(function() { standaloneMixinResult.declares(property, value); });
365 | });
366 | });
367 |
368 | context('for an included mixin', function() {
369 | it('should not throw an error if mixin creates a declaration with the given property and value', function() {
370 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
371 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
372 | includedMixinResult.declares(property, value);
373 | });
374 |
375 | it('should throw an error if mixin creates a declaration with the given property and not the value', function() {
376 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
377 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
378 | assert.throws(function() { includedMixinResult.declares(property, 'blue'); });
379 | });
380 |
381 | it('should throw an error if the given property cannot be found in mixin output', function() {
382 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns(undefined);
383 | assert.throws(function() { includedMixinResult.declares(property, value); });
384 | });
385 | });
386 | });
387 |
388 | describe('doesNotDeclare', function() {
389 | context('for a standalone mixin', function() {
390 | it('should throw an error if mixin creates a declaration with the given property and value', function() {
391 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
392 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
393 | assert.throws(function() { standaloneMixinResult.doesNotDeclare(property, value); });
394 | });
395 |
396 | it('should not throw an error if mixin creates a declaration with the given property and not the value', function() {
397 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
398 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
399 | standaloneMixinResult.doesNotDeclare(property, 'blue');
400 | });
401 |
402 | it('should not throw an error if the given property cannot be found in mixin output', function() {
403 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns(undefined);
404 | standaloneMixinResult.doesNotDeclare(property, value);
405 | });
406 | });
407 |
408 | context('for an included mixin', function() {
409 | it('should throw an error if mixin creates a declaration with the given property and value', function() {
410 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
411 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
412 | assert.throws(function() { includedMixinResult.doesNotDeclare(property, value); });
413 | });
414 |
415 | it('should not throw an error if mixin creates a declaration with the given property and not the value', function() {
416 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns({value: value});
417 | mockUtilities.expects('scrubQuotes').withArgs(value).returns(value);
418 | includedMixinResult.doesNotDeclare(property, 'blue');
419 | });
420 |
421 | it('should not throw an error if the given property cannot be found in mixin output', function() {
422 | mockParsers.expects('findDeclaration').withArgs(ast, property).returns(undefined);
423 | includedMixinResult.doesNotDeclare(property, value);
424 | });
425 | });
426 | });
427 |
428 | describe('equals', function() {
429 | context('for a standalone mixin', function() {
430 | it('should not throw an error if the output matches the input', function() {
431 | standaloneMixinResult.equals(wrappedResult);
432 | });
433 |
434 | it('throws an error if the output does not match the input', function() {
435 | assert.throws(function() { standaloneMixinResult.equals('.blah{color:red}'); });
436 | });
437 | });
438 |
439 | context('for an included mixin', function() {
440 | it('should not throw an error if the output matches the input', function() {
441 | includedMixinResult.equals(result);
442 | });
443 |
444 | it('throws an error if the output does not match the input', function() {
445 | assert.throws(function() { includedMixinResult.equals('.blah{color:red}'); });
446 | });
447 | });
448 | });
449 |
450 | describe('doesNotEqual', function() {
451 | context('for a standalone mixin', function() {
452 | it('should not throw an error if the output does not match the input', function() {
453 | standaloneMixinResult.doesNotEqual('color:red;');
454 | });
455 |
456 | it('throws an error if the output does match the input', function() {
457 | assert.throws(function() { standaloneMixinResult.doesNotEqual(wrappedResult); });
458 | });
459 | });
460 |
461 | context('for an included mixin', function() {
462 | it('should not throw an error if the output does not match the input', function() {
463 | includedMixinResult.doesNotEqual('color:red;');
464 | });
465 |
466 | it('throws an error if the output does match the input', function() {
467 | assert.throws(function() { includedMixinResult.doesNotEqual(result); });
468 | });
469 | });
470 | });
471 |
472 | describe('calls', function() {
473 | context('for a standalone mixin', function() {
474 | it('should not throw an error if the given call is included in the mixins call', function() {
475 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixin('standalone', call)).returns(wrappedResult);
476 | standaloneMixinResult.calls(call);
477 | });
478 |
479 | it('throws an error if the given call is not included in the mixins call', function() {
480 | var otherCall = 'other(blue)';
481 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixin('standalone', otherCall)).returns('.blah{color:red}');
482 | assert.throws(function() { standaloneMixinResult.calls(otherCall); });
483 | });
484 | });
485 |
486 | context('for an included mixin', function() {
487 | it('should not throw an error if the given call is included in the mixins call', function() {
488 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixin('included', call)).returns(wrappedResult);
489 | includedMixinResult.calls(call);
490 | });
491 |
492 | it('throws an error if the given call is not included in the mixins call', function() {
493 | var otherCall = 'other(blue)';
494 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixin('included', otherCall)).returns('.blah{color:red}');
495 | assert.throws(function() { includedMixinResult.calls(otherCall); });
496 | });
497 | });
498 | });
499 |
500 | describe('doesNotCall', function() {
501 | context('for a standalone mixin', function() {
502 | it('should not throw an error if the given call is not included in the mixins call', function() {
503 | var otherCall = 'other(blue)';
504 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixin('standalone', otherCall)).returns('.blah{color:red}');
505 | standaloneMixinResult.doesNotCall(otherCall);
506 | });
507 |
508 | it('throws an error if the given call is included in the mixins call', function() {
509 | mockUtilities.expects('createCss').withArgs(standaloneFile, MixinResult.wrapMixin('standalone', call)).returns(wrappedResult);
510 | assert.throws(function() { standaloneMixinResult.doesNotCall(call); });
511 | });
512 | });
513 |
514 | context('for an included mixin', function() {
515 | it('should not throw an error if the given call is not included in the mixins call', function() {
516 | var otherCall = 'other(blue)';
517 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixin('included', otherCall)).returns('.blah{color:red}');
518 | includedMixinResult.doesNotCall(otherCall);
519 | });
520 |
521 | it('throws an error if the given call is included in the mixins call', function() {
522 | mockUtilities.expects('createCss').withArgs(includedFile, MixinResult.wrapMixin('included', call)).returns(wrappedResult);
523 | assert.throws(function() { includedMixinResult.doesNotCall(call); });
524 | });
525 | });
526 | });
527 | });
528 |
--------------------------------------------------------------------------------
/test/mixinTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 |
7 | function MockMixinResult(type, file, call, args, block) {
8 | this.type = type;
9 | this.file = file;
10 | this.call = call;
11 | this.args = args;
12 | this.block = block;
13 | }
14 |
15 |
16 | describe('Mixin', function() {
17 | var Mixin;
18 | var mixin;
19 |
20 | var type = 'test';
21 | var variables = '$test:5;';
22 | var dependencies = "@import 'file';";
23 | var file = '@mixin test($input) { color: $input }';
24 | var call = '@include test(red)';
25 |
26 | beforeEach(function() {
27 | Mixin = proxyquire('../src/types/mixin', {
28 | '../mixinResult': MockMixinResult
29 | });
30 | mixin = new Mixin(type, variables, dependencies, file, call);
31 | });
32 |
33 | describe('new', function() {
34 | it('should set type, file, and call from the arguments', function() {
35 | assert.equal(mixin.type, type);
36 | assert.equal(mixin.file, variables + dependencies + file);
37 | assert.equal(mixin.call, call);
38 | });
39 | });
40 |
41 | describe('called', function() {
42 | it('should return a new mixinResult', function() {
43 | var result = mixin.called();
44 | assert(result instanceof MockMixinResult);
45 | });
46 |
47 | it('should have the correct properties', function() {
48 | var result = mixin.called();
49 | assert.equal(result.type, type);
50 | assert.equal(result.file, variables + dependencies + file);
51 | assert.equal(result.call, call);
52 | assert.deepEqual(result.args, undefined);
53 | assert.equal(result.block, undefined);
54 | });
55 | });
56 |
57 | describe('calledWith', function() {
58 | it('should return a new mixinResult', function() {
59 | var result = mixin.calledWith(1, 2, 'hello', true);
60 | assert(result instanceof MockMixinResult);
61 | });
62 |
63 | it('should have the correct properties', function() {
64 | var result = mixin.calledWith(1, 2, 'hello', true);
65 | assert.equal(result.type, type);
66 | assert.equal(result.file, variables + dependencies + file);
67 | assert.equal(result.call, call);
68 | assert.deepEqual(result.args, [1, 2, 'hello', true]);
69 | assert.equal(result.block, undefined);
70 | });
71 | });
72 |
73 | describe('calledWithArgs', function() {
74 | it('should return a new mixinResult', function() {
75 | var result = mixin.calledWithArgs(1, 2, 'hello', true);
76 | assert(result instanceof MockMixinResult);
77 | });
78 |
79 | it('should have the correct properties', function() {
80 | var result = mixin.calledWithArgs(1, 2, 'hello', true);
81 | assert.equal(result.type, type);
82 | assert.equal(result.file, variables + dependencies + file);
83 | assert.equal(result.call, call);
84 | assert.deepEqual(result.args, [1, 2, 'hello', true]);
85 | assert.equal(result.block, undefined);
86 | });
87 | });
88 |
89 | describe('calledWithBlock', function() {
90 | var block = 'color: red;';
91 |
92 | it('should return a new mixinResult', function() {
93 | var result = mixin.calledWithBlock(block);
94 | assert(result instanceof MockMixinResult);
95 | });
96 |
97 | it('should have the correct properties', function() {
98 | var result = mixin.calledWithBlock(block);
99 | assert.equal(result.type, type);
100 | assert.equal(result.file, variables + dependencies + file);
101 | assert.equal(result.call, call);
102 | assert.deepEqual(result.args, undefined);
103 | assert.equal(result.block, block);
104 | });
105 | });
106 |
107 | describe('calledWithBlockAndArgs', function() {
108 | var block = 'color: red;';
109 |
110 | it('should return a new mixinResult', function() {
111 | var result = mixin.calledWithBlockAndArgs(block, 1, 2, 'hello', true);
112 | assert(result instanceof MockMixinResult);
113 | });
114 |
115 | it('should have the correct properties', function() {
116 | var result = mixin.calledWithBlockAndArgs(block, 1, 2, 'hello', true);
117 | assert.equal(result.type, type);
118 | assert.equal(result.file, variables + dependencies + file);
119 | assert.equal(result.call, call);
120 | assert.deepEqual(result.args, [1, 2, 'hello', true]);
121 | assert.equal(result.block, block);
122 | });
123 | });
124 | });
125 |
--------------------------------------------------------------------------------
/test/parsersTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var parsers = require('../src/parsers');
6 | var ast = require('./fixtures/ast.json');
7 | var astNoSelectors = require('./fixtures/astNoSelectors.json');
8 | var astMediaQuery = require('./fixtures/astMediaQuery.json');
9 | var astMediaQueryFontFace = require('./fixtures/astMediaQueryFontFace.json');
10 |
11 | describe('Parsers', function() {
12 | describe('escapeCharacters', function() {
13 | it('should escape all special characters that can be used in a filename', function() {
14 | assert.equal(parsers.escapeCharacters('test/this'), 'test\/this');
15 | assert.equal(parsers.escapeCharacters('test.this'), 'test\.this');
16 | assert.equal(parsers.escapeCharacters('test*this'), 'test\*this');
17 | assert.equal(parsers.escapeCharacters('test?this'), 'test\?this');
18 | assert.equal(parsers.escapeCharacters('test(this'), 'test\(this');
19 | assert.equal(parsers.escapeCharacters('test)this'), 'test\)this');
20 | assert.equal(parsers.escapeCharacters('test{this'), 'test\{this');
21 | assert.equal(parsers.escapeCharacters('test}this'), 'test\}this');
22 | });
23 | });
24 |
25 | describe('hasSelectorValue', function() {
26 | var ruleWithoutSelectors = {};
27 | var rule = {
28 | type: 'rule',
29 | selectors: ['.test', '.second']
30 | };
31 |
32 | it('should return true if the selector is in the rule', function() {
33 | assert(parsers.hasSelectorValue(rule, '.test'));
34 | assert(parsers.hasSelectorValue(rule, '.second'));
35 | });
36 |
37 | it('should return false if the selector is not in the rule', function() {
38 | assert(!parsers.hasSelectorValue(rule, '.blah'));
39 | });
40 |
41 | it('should return false if the rule has no selectors', function() {
42 | assert(!parsers.hasSelectorValue(ruleWithoutSelectors, '.test'));
43 | });
44 | });
45 |
46 | describe('findDeclarationProperty', function() {
47 | var declaration = {
48 | 'type': 'declaration',
49 | 'property': 'color'
50 | };
51 | var ruleWithoutDeclarations = {
52 | type: 'rule',
53 | selectors: ['.test']
54 | };
55 | var ruleWithoutDeclaration = {
56 | type: 'rule',
57 | selectors: ['.test'],
58 | declarations: []
59 | };
60 | var rule = {
61 | type: 'rule',
62 | selectors: ['.test'],
63 | declarations: [declaration]
64 | };
65 |
66 | it('should return the declaration if it is in the rule', function() {
67 | assert.equal(parsers.findDeclarationProperty(rule, 'color'), declaration);
68 | });
69 |
70 | it('should return undefined if the declaration is not in the rule', function() {
71 | assert.equal(parsers.findDeclarationProperty(ruleWithoutDeclaration, 'color'), undefined);
72 | });
73 |
74 | it('should return undefined if the rule has no declarations', function() {
75 | assert.equal(parsers.findDeclarationProperty(ruleWithoutDeclarations, 'color'), undefined);
76 | });
77 | });
78 |
79 | describe('isFontFace', function() {
80 | var font = {
81 | type: 'font-face'
82 | };
83 |
84 | var media = {
85 | type: 'media'
86 | };
87 |
88 | it('should return true if the rule is a font-face type', function() {
89 | assert(parsers.isFontFace(font));
90 | });
91 |
92 | it('should return false if the rule is not a font-face type', function() {
93 | assert(!parsers.isFontFace(media));
94 | });
95 | });
96 |
97 | describe('countDeclarations', function() {
98 | it('should return the number of declarations in output', function() {
99 | assert.equal(parsers.countDeclarations(ast), 4);
100 | });
101 |
102 | it('should return 0 if there are no declarations', function() {
103 | assert.equal(parsers.countDeclarations({stylesheet: {rules: [{declarations: []}]}}), 0);
104 | });
105 | });
106 |
107 | describe('findDeclaration', function() {
108 | it('should return the declaration object if the property is found', function() {
109 | var declaration = {
110 | 'type': 'declaration',
111 | 'property': 'background-color',
112 | 'value': 'blue',
113 | 'position': {
114 | 'start': {
115 | 'line': 7,
116 | 'column': 3
117 | },
118 | 'end': {
119 | 'line': 7,
120 | 'column': 25
121 | }
122 | }
123 | };
124 | assert.deepEqual(parsers.findDeclaration(ast, 'background-color'), declaration);
125 | });
126 |
127 | it('should return the first declaration object if two of the property are found', function() {
128 | var declaration = {
129 | 'type': 'declaration',
130 | 'property': 'color',
131 | 'value': 'red',
132 | 'position': {
133 | 'start': {
134 | 'line': 2,
135 | 'column': 3
136 | },
137 | 'end': {
138 | 'line': 2,
139 | 'column': 13
140 | }
141 | }
142 | };
143 | assert.deepEqual(parsers.findDeclaration(ast, 'color'), declaration);
144 | });
145 |
146 | it('should return undefined if the declaration is not found', function() {
147 | assert.equal(parsers.findDeclaration(ast, 'display'), undefined);
148 | });
149 | });
150 |
151 | describe('findMedia', function() {
152 | it('should return the first rule of the type media', function() {
153 | var media = parsers.findMedia(astMediaQuery);
154 | assert.equal(media.type, 'media');
155 | assert.equal(media.media, 'screen and (min-width: 600px)');
156 | });
157 |
158 | it('should return undefined if there are no rules of the type media', function() {
159 | var media = parsers.findMedia(ast);
160 | assert.deepEqual(media, undefined);
161 | });
162 | });
163 |
164 | describe('hasSelector', function() {
165 | it('should return true if the selector is defined by itself', function() {
166 | assert(parsers.hasSelector(ast, '.test'));
167 | });
168 |
169 | it('should return true if the selector is defined in a list', function() {
170 | assert(parsers.hasSelector(ast, '.hello'));
171 | assert(parsers.hasSelector(ast, '.blah'));
172 | });
173 |
174 | it('should return true if the selector is defined in a media query', function() {
175 | assert(parsers.hasSelector(astMediaQuery, 'body'));
176 | });
177 |
178 | it('should return false if the selector not defined', function() {
179 | assert(!parsers.hasSelector(ast, '.not-defined'));
180 | });
181 |
182 | it('should return false if no stylesheet is defined', function() {
183 | assert(!parsers.hasSelector(astNoSelectors, '.test'));
184 | });
185 |
186 | it('should return false if no rules are defined', function() {
187 | assert(!parsers.hasSelector(astNoSelectors, '.test'));
188 | });
189 | });
190 |
191 | describe('hasFontFace', function() {
192 | it('should return true if font-face is defined', function() {
193 | assert(parsers.hasFontFace(astNoSelectors));
194 | });
195 |
196 | it('should return true if font-face is defined inside a media query', function() {
197 | assert(parsers.hasFontFace(astMediaQueryFontFace));
198 | });
199 |
200 | it('should return false if font-face not defined', function() {
201 | assert(!parsers.hasFontFace(ast));
202 | });
203 | });
204 |
205 | describe('hasImport', function() {
206 | var name = 'test';
207 |
208 | context('should return true if the import exists', function() {
209 | it('if it uses single quotes', function() {
210 | var sass = "@import '" + name + "';";
211 | assert(parsers.hasImport(sass, name));
212 | });
213 |
214 | it('if it is nested in a directory', function() {
215 | var sass = "@import 'testing/" + name + "';";
216 | assert(parsers.hasImport(sass, 'testing/' + name));
217 | });
218 |
219 | it('if it uses double quotes', function() {
220 | var sass = '@import "' + name + '";';
221 | assert(parsers.hasImport(sass, name));
222 | });
223 |
224 | it('if it has no space', function() {
225 | var sass = '@import"' + name + '";';
226 | assert(parsers.hasImport(sass, name));
227 | });
228 |
229 | it('if it has multiple on the same line', function() {
230 | var sass = '@import "hello"; @import"' + name + '";';
231 | assert(parsers.hasImport(sass, name));
232 | });
233 |
234 | it('if it has multiple on different lines', function() {
235 | var sass = '@import "hello";\n@import"' + name + '";';
236 | assert(parsers.hasImport(sass, name));
237 | });
238 |
239 | it('if it has multiple in a comma separated list', function() {
240 | var sass = '@import "hello", "' + name + '";';
241 | assert(parsers.hasImport(sass, name));
242 | });
243 | });
244 |
245 | context('should return false if the import does not exist', function() {
246 | var alternate = 'nope';
247 |
248 | it('if it uses single quotes', function() {
249 | var sass = "@import '" + alternate + "';";
250 | assert(!parsers.hasImport(sass, name));
251 | });
252 |
253 | it('if it uses double quotes', function() {
254 | var sass = '@import "' + alternate + '";';
255 | assert(!parsers.hasImport(sass, name));
256 | });
257 |
258 | it('if it has no space', function() {
259 | var sass = '@import"' + alternate + '";';
260 | assert(!parsers.hasImport(sass, name));
261 | });
262 |
263 | it('if it has multiple on the same line', function() {
264 | var sass = '@import "hello"; @import"' + alternate + '";';
265 | assert(!parsers.hasImport(sass, name));
266 | });
267 |
268 | it('if it has multiple on different lines', function() {
269 | var sass = '@import "hello";\n@import"' + alternate + '";';
270 | assert(!parsers.hasImport(sass, name));
271 | });
272 |
273 | it('if it has multiple in a comma separated list', function() {
274 | var sass = '@import "hello", "' + alternate + '";';
275 | assert(!parsers.hasImport(sass, name));
276 | });
277 | });
278 | });
279 | });
280 |
--------------------------------------------------------------------------------
/test/sassabyTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 | var Mixin = require('../src/types/mixin');
7 | var Func = require('../src/types/func');
8 | var parsers = require('../src/parsers');
9 |
10 | describe('Sassaby', function() {
11 | var Sassaby;
12 | var readFileSync;
13 | var mockParsers;
14 | var filename = 'file.scss';
15 | var dependency = 'depends-on-this.scss';
16 | var fileContents = 'File Contents!!!!!!';
17 |
18 | beforeEach(function() {
19 | readFileSync = sinon.spy(function(filename) { return fileContents; });
20 | Sassaby = proxyquire('../src/sassaby', {
21 | 'fs': { 'readFileSync': readFileSync },
22 | './parsers': parsers
23 | });
24 |
25 | mockParsers = sinon.mock(parsers);
26 | });
27 |
28 | afterEach(function() {
29 | mockParsers.restore();
30 | });
31 |
32 | describe('setVariables', function() {
33 | it('should return a string of the SASS variable declaration if given one variable', function() {
34 | var variables = {
35 | 'color': 'blue'
36 | };
37 | assert.equal(Sassaby.setVariables(variables), '$color:blue;');
38 | });
39 |
40 | it('should return a string of SASS variable declarations if given more than one variable', function() {
41 | var variables = {
42 | 'color': 'blue',
43 | 'font-size': '16px'
44 | };
45 | assert.equal(Sassaby.setVariables(variables), '$color:blue;$font-size:16px;');
46 | });
47 | });
48 |
49 | describe('setDependencies', function() {
50 | it('should return a string of the SASS import if given one dependency', function() {
51 | var dependencies = [
52 | 'firstImport'
53 | ];
54 | assert.equal(Sassaby.setDependencies(dependencies), "@import 'firstImport';");
55 | });
56 |
57 | it('should return a string of SASS imports if given more than one dependency', function() {
58 | var dependencies = [
59 | 'firstImport',
60 | 'secondImport'
61 | ];
62 | assert.equal(Sassaby.setDependencies(dependencies), "@import 'firstImport';@import 'secondImport';");
63 | });
64 | });
65 |
66 | describe('new', function() {
67 | it('should set the correct properties when only passed a path', function() {
68 | var sassaby = new Sassaby(filename);
69 | assert(readFileSync.calledWith(filename));
70 | assert.equal(sassaby.path, filename);
71 | assert.equal(sassaby.file, fileContents);
72 | assert.equal(sassaby.variables, '');
73 | assert.equal(sassaby.dependencies, '');
74 | });
75 |
76 | it('should set the correct properties when passed a path and variables', function() {
77 | var sassaby = new Sassaby(filename, {
78 | variables: {
79 | 'test': 1
80 | }
81 | });
82 | assert(readFileSync.calledWith(filename));
83 | assert.equal(sassaby.path, filename);
84 | assert.equal(sassaby.file, fileContents);
85 | assert.equal(sassaby.variables, '$test:1;');
86 | assert.equal(sassaby.dependencies, '');
87 | });
88 |
89 | it('should set the correct properties when passed a path and dependencies', function() {
90 | var sassaby = new Sassaby(filename, {
91 | dependencies: [
92 | dependency
93 | ]
94 | });
95 | assert(readFileSync.calledWith(filename));
96 | assert.equal(sassaby.path, filename);
97 | assert.equal(sassaby.file, fileContents);
98 | assert.equal(sassaby.variables, '');
99 | assert.equal(sassaby.dependencies, "@import '" + dependency + "';");
100 | });
101 |
102 | it('should set the correct properties when passed a path, variables, and dependencies', function() {
103 | var sassaby = new Sassaby(filename, {
104 | variables: {
105 | 'test': 1
106 | },
107 | dependencies: [
108 | dependency
109 | ]
110 | });
111 | assert(readFileSync.calledWith(filename));
112 | assert.equal(sassaby.path, filename);
113 | assert.equal(sassaby.file, fileContents);
114 | assert.equal(sassaby.variables, '$test:1;');
115 | assert.equal(sassaby.dependencies, "@import '" + dependency + "';");
116 | });
117 | });
118 |
119 | describe('imports', function() {
120 | var name = 'test';
121 |
122 | it('should not throw an error if parsers.hasImport is true', function() {
123 | mockParsers.expects('hasImport').withArgs(fileContents, name).returns(true);
124 | var sassaby = new Sassaby(filename);
125 | sassaby.imports(name);
126 | });
127 |
128 | it('throws an error if parsers.hasImport is false', function() {
129 | mockParsers.expects('hasImport').withArgs(fileContents, name).returns(false);
130 | var sassaby = new Sassaby(filename);
131 | assert.throws(function() { sassaby.imports(name); });
132 | });
133 | });
134 |
135 | describe('doesNotImport', function() {
136 | var name = 'test';
137 |
138 | it('should throw an error if parsers.hasImport is true', function() {
139 | mockParsers.expects('hasImport').withArgs(fileContents, name).returns(true);
140 | var sassaby = new Sassaby(filename);
141 | assert.throws(function() { sassaby.doesNotImport(name); });
142 | });
143 |
144 | it('should not throw an error if parsers.hasImport is false', function() {
145 | mockParsers.expects('hasImport').withArgs(fileContents, name).returns(false);
146 | var sassaby = new Sassaby(filename);
147 | sassaby.doesNotImport(name);
148 | });
149 | });
150 |
151 | describe('includedMixin', function() {
152 | it('return a new instance of Mixin', function() {
153 | var sassaby = new Sassaby(filename);
154 | var call = '@include test(blue);';
155 | assert(sassaby.includedMixin(call) instanceof Mixin);
156 | });
157 | });
158 |
159 | describe('standaloneMixin', function() {
160 | it('return a new instance of Mixin', function() {
161 | var sassaby = new Sassaby(filename);
162 | var call = '@include test(blue);';
163 | assert(sassaby.standaloneMixin(call) instanceof Mixin);
164 | });
165 | });
166 |
167 | describe('func', function() {
168 | it('return a new instance of Func', function() {
169 | var sassaby = new Sassaby(filename);
170 | var call = 'test(blue);';
171 | assert(sassaby.func(call) instanceof Func);
172 | });
173 | });
174 | });
175 |
--------------------------------------------------------------------------------
/test/utilitiesTests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var sinon = require('sinon');
5 | var proxyquire = require('proxyquire');
6 |
7 |
8 | describe('Utilities', function() {
9 | var renderSync;
10 | var cssmin;
11 | var parse;
12 | var utilities;
13 |
14 | var file = '@mixin test($input) { color: $input }';
15 | var string = 'test(red)';
16 | var combined = file + string;
17 |
18 | beforeEach(function() {
19 | renderSync = sinon.spy(function(options) { return { css: options.data }; });
20 | cssmin = sinon.spy(function(input) { return input; });
21 | parse = sinon.spy(function(input) { return input; });
22 | utilities = proxyquire('../src/utilities', {
23 | 'node-sass': { 'renderSync': renderSync },
24 | 'cssmin': cssmin,
25 | 'css': { 'parse': parse }
26 | });
27 | });
28 |
29 | describe('compileFromString', function() {
30 | it('should call sass.renderSync with the given string string and return the compilation', function() {
31 | assert.equal(utilities.compileFromString(combined), combined);
32 | renderSync.calledWith({data: combined});
33 | });
34 | });
35 |
36 | describe('compileWithFile', function() {
37 | it('should call compileFromString with the combined css of the inputs', function() {
38 | sinon.mock(utilities).expects('compileFromString').withArgs(combined).returns(combined);
39 | assert.equal(utilities.compileWithFile(file, string), combined);
40 | });
41 | });
42 |
43 | describe('createCss', function() {
44 | it('should minify the result of compileWithFile', function() {
45 | sinon.mock(utilities).expects('compileWithFile').withArgs(file, string).returns(combined);
46 | assert.equal(utilities.createCss(file, string), combined);
47 | cssmin.calledWith(combined);
48 | });
49 | });
50 |
51 | describe('createAst', function() {
52 | var css = '.test { color: red; }';
53 |
54 | it('should parse the given css', function() {
55 | assert.equal(utilities.createAst(css), css);
56 | parse.calledWith(combined);
57 | });
58 | });
59 |
60 | describe('scrubQuotes', function() {
61 | it('should remove double quotes from the inputted string', function() {
62 | assert.equal(utilities.scrubQuotes('"hello"'), 'hello');
63 | });
64 |
65 | it('should remove single quotes from the inputted string', function() {
66 | assert.equal(utilities.scrubQuotes("'hello'"), 'hello');
67 | });
68 |
69 | it('should remove both quotes from the inputted string', function() {
70 | assert.equal(utilities.scrubQuotes("'\"hello\"'"), 'hello');
71 | });
72 | });
73 |
74 | describe('concatArgs', function() {
75 | it('should return the args in the given array as a comma separated string', function() {
76 | var args = [1, 2, 'hello', true];
77 | assert.equal(utilities.concatArgs(args), '1, 2, hello, true');
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------