spec.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. var assert = require('assert'),
  2. fs = require('fs'),
  3. exists = fs.existsSync,
  4. join = require('path').join,
  5. read = fs.readFileSync,
  6. sass = process.env.NODESASS_COV
  7. ? require('../lib-cov')
  8. : require('../lib'),
  9. readYaml = require('read-yaml'),
  10. mergeWith = require('lodash/mergeWith'),
  11. assign = require('lodash/assign'),
  12. glob = require('glob'),
  13. specPath = require('sass-spec').dirname.replace(/\\/g, '/'),
  14. impl = 'libsass',
  15. version = 3.5;
  16. var normalize = function(str) {
  17. // This should be /\r\n/g, '\n', but there seems to be some empty line issues
  18. return str.replace(/\s+/g, '');
  19. };
  20. var inputs = glob.sync(specPath + '/**/input.*');
  21. var initialize = function(inputCss, options) {
  22. var testCase = {};
  23. var folders = inputCss.split('/');
  24. var folder = join(inputCss, '..');
  25. testCase.folder = folder;
  26. testCase.name = folders[folders.length - 2];
  27. testCase.inputPath = inputCss;
  28. testCase.expectedPath = join(folder, 'expected_output.css');
  29. testCase.errorPath = join(folder, 'error');
  30. testCase.statusPath = join(folder, 'status');
  31. testCase.optionsPath = join(folder, 'options.yml');
  32. if (exists(testCase.optionsPath)) {
  33. options = mergeWith(assign({}, options), readYaml.sync(testCase.optionsPath), customizer);
  34. }
  35. testCase.includePaths = [
  36. folder,
  37. join(folder, 'sub')
  38. ];
  39. testCase.precision = parseFloat(options[':precision']) || 5;
  40. testCase.outputStyle = options[':output_style'] ? options[':output_style'].replace(':', '') : 'nested';
  41. testCase.todo = options[':todo'] !== undefined && options[':todo'] !== null && options[':todo'].indexOf(impl) !== -1;
  42. testCase.only = options[':only_on'] !== undefined && options[':only_on'] !== null && options[':only_on'];
  43. testCase.warningTodo = options[':warning_todo'] !== undefined && options[':warning_todo'] !== null && options[':warning_todo'].indexOf(impl) !== -1;
  44. testCase.startVersion = parseFloat(options[':start_version']) || 0;
  45. testCase.endVersion = parseFloat(options[':end_version']) || 99;
  46. testCase.options = options;
  47. testCase.result = false;
  48. // Probe filesystem once and cache the results
  49. testCase.shouldFail = exists(testCase.statusPath) && !fs.statSync(testCase.statusPath).isDirectory();
  50. testCase.verifyStderr = exists(testCase.errorPath) && !fs.statSync(testCase.errorPath).isDirectory();
  51. return testCase;
  52. };
  53. var runTest = function(inputCssPath, options) {
  54. var test = initialize(inputCssPath, options);
  55. it(test.name, function(done) {
  56. if (test.todo || test.warningTodo) {
  57. this.skip('Test marked with TODO');
  58. } else if (test.only && test.only.indexOf(impl) === -1) {
  59. this.skip('Tests marked for only: ' + test.only.join(', '));
  60. } else if (version < test.startVersion) {
  61. this.skip('Tests marked for newer Sass versions only');
  62. } else if (version > test.endVersion) {
  63. this.skip('Tests marked for older Sass versions only');
  64. } else {
  65. var expected = normalize(read(test.expectedPath, 'utf8'));
  66. sass.render({
  67. file: test.inputPath,
  68. includePaths: test.includePaths,
  69. precision: test.precision,
  70. outputStyle: test.outputStyle
  71. }, function(error, result) {
  72. if (test.shouldFail) {
  73. // Replace 1, with parseInt(read(test.statusPath, 'utf8')) pending
  74. // https://github.com/sass/libsass/issues/2162
  75. assert.equal(error.status, 1);
  76. } else if (test.verifyStderr) {
  77. var expectedError = read(test.errorPath, 'utf8');
  78. if (error === null) {
  79. // Probably a warning
  80. assert.ok(expectedError, 'Expected some sort of warning, but found none');
  81. } else {
  82. // The error messages seem to have some differences in areas
  83. // like line numbering, so we'll check the first line for the
  84. // general errror message only
  85. assert.equal(
  86. error.formatted.toString().split('\n')[0],
  87. expectedError.toString().split('\n')[0],
  88. 'Should Error.\nOptions' + JSON.stringify(test.options));
  89. }
  90. } else if (expected) {
  91. assert.equal(
  92. normalize(result.css.toString()),
  93. expected,
  94. 'Should equal with options ' + JSON.stringify(test.options)
  95. );
  96. }
  97. done();
  98. });
  99. }
  100. });
  101. };
  102. var specSuite = {
  103. name: specPath.split('/').slice(-1)[0],
  104. folder: specPath,
  105. tests: [],
  106. suites: [],
  107. options: {}
  108. };
  109. function customizer(objValue, srcValue) {
  110. if (Array.isArray(objValue)) {
  111. return objValue.concat(srcValue);
  112. }
  113. }
  114. var executeSuite = function(suite, tests) {
  115. var suiteFolderLength = suite.folder.split('/').length;
  116. var optionsFile = join(suite.folder, 'options.yml');
  117. if (exists(optionsFile)) {
  118. suite.options = mergeWith(assign({}, suite.options), readYaml.sync(optionsFile), customizer);
  119. }
  120. // Push tests in the current suite
  121. tests = tests.filter(function(test) {
  122. var testSuiteFolder = test.split('/');
  123. var inputSass = testSuiteFolder[suiteFolderLength + 1];
  124. // Add the test if the specPath matches the testname
  125. if (inputSass === 'input.scss' || inputSass === 'input.sass') {
  126. suite.tests.push(test);
  127. } else {
  128. return test;
  129. }
  130. });
  131. if (tests.length !== 0) {
  132. var prevSuite = tests[0].split('/')[suiteFolderLength];
  133. var suiteName = '';
  134. var prevSuiteStart = 0;
  135. for (var i = 0; i < tests.length; i++) {
  136. var test = tests[i];
  137. suiteName = test.split('/')[suiteFolderLength];
  138. if (suiteName !== prevSuite) {
  139. suite.suites.push(
  140. executeSuite(
  141. {
  142. name: prevSuite,
  143. folder: suite.folder + '/' + prevSuite,
  144. tests: [],
  145. suites: [],
  146. options: assign({}, suite.options),
  147. },
  148. tests.slice(prevSuiteStart, i)
  149. )
  150. );
  151. prevSuite = suiteName;
  152. prevSuiteStart = i;
  153. }
  154. }
  155. suite.suites.push(
  156. executeSuite(
  157. {
  158. name: suiteName,
  159. folder: suite.folder + '/' + suiteName,
  160. tests: [],
  161. suites: [],
  162. options: assign({}, suite.options),
  163. },
  164. tests.slice(prevSuiteStart, tests.length)
  165. )
  166. );
  167. }
  168. return suite;
  169. };
  170. var allSuites = executeSuite(specSuite, inputs);
  171. var runSuites = function(suite) {
  172. describe(suite.name, function(){
  173. suite.tests.forEach(function(test){
  174. runTest(test, suite.options);
  175. });
  176. suite.suites.forEach(function(subsuite) {
  177. runSuites(subsuite);
  178. });
  179. });
  180. };
  181. runSuites(allSuites);