spec.js 6.0 KB

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