less 4.4.1 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/Gruntfile.js +16 -17
  2. package/bin/lessc +35 -40
  3. package/dist/less.js +125 -186
  4. package/dist/less.min.js +2 -2
  5. package/dist/less.min.js.map +1 -1
  6. package/lib/less/contexts.js +0 -1
  7. package/lib/less/contexts.js.map +1 -1
  8. package/lib/less/default-options.js +22 -3
  9. package/lib/less/default-options.js.map +1 -1
  10. package/lib/less/less-error.js +2 -1
  11. package/lib/less/less-error.js.map +1 -1
  12. package/lib/less/parse-tree.js +58 -1
  13. package/lib/less/parse-tree.js.map +1 -1
  14. package/lib/less/parser/parser-input.js +2 -19
  15. package/lib/less/parser/parser-input.js.map +1 -1
  16. package/lib/less/parser/parser.js +8 -14
  17. package/lib/less/parser/parser.js.map +1 -1
  18. package/lib/less/source-map-output.js +1 -1
  19. package/lib/less/source-map-output.js.map +1 -1
  20. package/lib/less/tree/debug-info.js +29 -0
  21. package/lib/less/tree/debug-info.js.map +1 -1
  22. package/lib/less/tree/nested-at-rule.js +1 -1
  23. package/lib/less/tree/nested-at-rule.js.map +1 -1
  24. package/lib/less-node/lessc-helper.js +8 -7
  25. package/lib/less-node/lessc-helper.js.map +1 -1
  26. package/package.json +10 -5
  27. package/scripts/coverage-lines.js +207 -0
  28. package/scripts/coverage-report.js +158 -0
  29. package/scripts/postinstall.js +61 -0
  30. package/test/browser/common.js +22 -1
  31. package/test/browser/generator/runner.config.js +20 -24
  32. package/test/browser/generator/template.js +14 -3
  33. package/test/browser/runner-browser-options.js +5 -5
  34. package/test/index.js +293 -98
  35. package/test/less-test.js +453 -94
  36. package/test/sourcemaps/comprehensive.json +1 -0
  37. package/test/sourcemaps/sourcemaps-basepath.json +1 -0
  38. package/test/sourcemaps/sourcemaps-include-source.json +1 -0
  39. package/test/sourcemaps/sourcemaps-rootpath.json +1 -0
  40. package/test/sourcemaps/sourcemaps-url.json +1 -0
  41. package/test.less +1 -0
  42. package/tsconfig.json +20 -20
  43. package/lib/less/parser/chunker.js +0 -148
  44. package/lib/less/parser/chunker.js.map +0 -1
  45. package/test/browser/runner-legacy-options.js +0 -6
  46. package/test/browser/runner-legacy-spec.js +0 -3
package/test/index.js CHANGED
@@ -1,112 +1,307 @@
1
- var lessTest = require('./less-test'),
2
- lessTester = lessTest(),
3
- path = require('path'),
4
- stylize = require('../lib/less-node/lessc-helper').stylize,
5
- nock = require('nock');
1
+ // Mock needle for HTTP requests BEFORE any other requires
2
+ const Module = require('module');
3
+ const originalRequire = Module.prototype.require;
4
+ Module.prototype.require = function(id) {
5
+ if (id === 'needle') {
6
+ return {
7
+ get: function(url, options, callback) {
8
+
9
+ // Handle CDN requests
10
+ if (url.includes('cdn.jsdelivr.net')) {
11
+ if (url.includes('selectors.less')) {
12
+ setTimeout(() => {
13
+ callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/selectors/selectors.less'), 'utf8'));
14
+ }, 10);
15
+ return;
16
+ }
17
+ if (url.includes('media.less')) {
18
+ setTimeout(() => {
19
+ callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/media/media.less'), 'utf8'));
20
+ }, 10);
21
+ return;
22
+ }
23
+ if (url.includes('empty.less')) {
24
+ setTimeout(() => {
25
+ callback(null, { statusCode: 200 }, fs.readFileSync(path.join(__dirname, '../../test-data/tests-unit/empty/empty.less'), 'utf8'));
26
+ }, 10);
27
+ return;
28
+ }
29
+ }
30
+
31
+ // Handle redirect test - simulate needle's automatic redirect handling
32
+ if (url.includes('example.com/redirect.less')) {
33
+ setTimeout(() => {
34
+ // Simulate the final response after needle automatically follows the redirect
35
+ callback(null, { statusCode: 200 }, 'h1 { color: blue; }');
36
+ }, 10);
37
+ return;
38
+ }
39
+
40
+ if (url.includes('example.com/target.less')) {
41
+ setTimeout(() => {
42
+ callback(null, { statusCode: 200 }, 'h1 { color: blue; }');
43
+ }, 10);
44
+ return;
45
+ }
46
+
47
+ // Default error for unmocked URLs
48
+ setTimeout(() => {
49
+ callback(new Error('Unmocked URL: ' + url), null, null);
50
+ }, 10);
51
+ }
52
+ };
53
+ }
54
+ return originalRequire.apply(this, arguments);
55
+ };
56
+
57
+ // Now load other modules after mocking is set up
58
+ var path = require('path'),
59
+ fs = require('fs'),
60
+ lessTest = require('./less-test'),
61
+ stylize = require('../lib/less-node/lessc-helper').stylize;
62
+
63
+ // Parse command line arguments for test filtering
64
+ var args = process.argv.slice(2);
65
+ var testFilter = args.length > 0 ? args[0] : null;
66
+
67
+ // Create the test runner with the filter
68
+ var lessTester = lessTest(testFilter);
69
+
70
+ // HTTP mocking is now handled by needle mocking above
71
+
72
+ // Test HTTP redirect functionality
73
+ function testHttpRedirects() {
74
+ const less = require('../lib/less-node').default;
75
+
76
+ console.log('🧪 Testing HTTP redirect functionality...');
77
+
78
+ const redirectTest = `
79
+ @import "https://example.com/redirect.less";
80
+
81
+ h1 { color: red; }
82
+ `;
83
+
84
+ return less.render(redirectTest, {
85
+ filename: 'test-redirect.less'
86
+ }).then(result => {
87
+ console.log('✅ HTTP redirect test SUCCESS:');
88
+ console.log(result.css);
89
+
90
+ // Check if both imported and local content are present
91
+ if (result.css.includes('color: blue') && result.css.includes('color: red')) {
92
+ console.log('🎉 HTTP redirect test PASSED - both imported and local content found');
93
+ return true;
94
+ } else {
95
+ console.log('❌ HTTP redirect test FAILED - missing expected content');
96
+ return false;
97
+ }
98
+ }).catch(err => {
99
+ console.log('❌ HTTP redirect test ERROR:');
100
+ console.log(err.message);
101
+ return false;
102
+ });
103
+ }
104
+
105
+ // Test import-remote functionality
106
+ function testImportRemote() {
107
+ const less = require('../lib/less-node').default;
108
+ const fs = require('fs');
109
+ const path = require('path');
110
+
111
+ console.log('🧪 Testing import-remote functionality...');
112
+
113
+ const testFile = path.join(__dirname, '../../test-data/tests-unit/import/import-remote.less');
114
+ const expectedFile = path.join(__dirname, '../../test-data/tests-unit/import/import-remote.css');
115
+
116
+ const content = fs.readFileSync(testFile, 'utf8');
117
+ const expected = fs.readFileSync(expectedFile, 'utf8');
118
+
119
+ return less.render(content, {
120
+ filename: testFile
121
+ }).then(result => {
122
+ console.log('✅ Import-remote test SUCCESS:');
123
+ console.log('Expected:', expected.trim());
124
+ console.log('Actual:', result.css.trim());
125
+
126
+ if (result.css.trim() === expected.trim()) {
127
+ console.log('🎉 Import-remote test PASSED - CDN imports and variable resolution working');
128
+ return true;
129
+ } else {
130
+ console.log('❌ Import-remote test FAILED - output mismatch');
131
+ return false;
132
+ }
133
+ }).catch(err => {
134
+ console.log('❌ Import-remote test ERROR:');
135
+ console.log(err.message);
136
+ return false;
137
+ });
138
+ }
6
139
 
7
140
  console.log('\n' + stylize('Less', 'underline') + '\n');
8
141
 
142
+ if (testFilter) {
143
+ console.log('Running tests matching: ' + testFilter + '\n');
144
+ }
145
+
146
+ // Glob patterns for main test runs (excluding problematic tests that will run separately)
147
+ var globPatterns = [
148
+ 'tests-config/*/*.less',
149
+ 'tests-unit/*/*.less',
150
+ // Debug tests have nested subdirectories (comments/, mediaquery/, all/)
151
+ 'tests-config/debug/*/linenumbers-*.less',
152
+ '!tests-config/sourcemaps/**/*.less', // Exclude sourcemaps (need special handling)
153
+ '!tests-config/sourcemaps-empty/*', // Exclude sourcemaps-empty (need special handling)
154
+ '!tests-config/sourcemaps-disable-annotation/*', // Exclude sourcemaps-disable-annotation (need special handling)
155
+ '!tests-config/sourcemaps-variable-selector/*', // Exclude sourcemaps-variable-selector (need special handling)
156
+ '!tests-config/globalVars/*', // Exclude globalVars (need JSON config handling)
157
+ '!tests-config/modifyVars/*', // Exclude modifyVars (need JSON config handling)
158
+ '!tests-config/js-type-errors/*', // Exclude js-type-errors (need special test function)
159
+ '!tests-config/no-js-errors/*', // Exclude no-js-errors (need special test function)
160
+ '!tests-unit/import/import-remote.less', // Exclude import-remote (tested separately in isolation)
161
+
162
+ // HTTP import tests are now included since we have needle mocking
163
+ ];
164
+
9
165
  var testMap = [
10
- [{
11
- // TODO: Change this to rewriteUrls: 'all' once the relativeUrls option is removed
12
- relativeUrls: true,
13
- silent: true,
14
- javascriptEnabled: true
15
- }, '_main/'],
16
- [{}, 'namespacing/'],
17
- [{
18
- math: 'parens'
19
- }, 'math/strict/'],
20
- [{
21
- math: 'parens-division'
22
- }, 'math/parens-division/'],
23
- [{
24
- math: 'always'
25
- }, 'math/always/'],
26
- // Use legacy strictMath: true here to demonstrate it still works
27
- [{strictMath: true, strictUnits: true, javascriptEnabled: true}, '../errors/eval/',
28
- lessTester.testErrors, null],
29
- [{strictMath: true, strictUnits: true, javascriptEnabled: true}, '../errors/parse/',
30
- lessTester.testErrors, null],
31
- [{math: 'strict', strictUnits: true, javascriptEnabled: true}, 'js-type-errors/',
32
- lessTester.testTypeErrors, null],
33
- [{math: 'strict', strictUnits: true, javascriptEnabled: false}, 'no-js-errors/',
34
- lessTester.testErrors, null],
35
- [{math: 'strict', dumpLineNumbers: 'comments'}, 'debug/', null,
36
- function(name) { return name + '-comments'; }],
37
- [{math: 'strict', dumpLineNumbers: 'mediaquery'}, 'debug/', null,
38
- function(name) { return name + '-mediaquery'; }],
39
- [{math: 'strict', dumpLineNumbers: 'all'}, 'debug/', null,
40
- function(name) { return name + '-all'; }],
41
- // TODO: Change this to rewriteUrls: false once the relativeUrls option is removed
42
- [{math: 'strict', relativeUrls: false, rootpath: 'folder (1)/'}, 'static-urls/'],
43
- [{math: 'strict', compress: true}, 'compression/'],
44
-
45
- [{math: 0, strictUnits: true}, 'units/strict/'],
46
- [{math: 0, strictUnits: false}, 'units/no-strict/'],
47
-
48
- [{math: 'strict', strictUnits: true, sourceMap: true, globalVars: true }, 'sourcemaps/',
49
- lessTester.testSourcemap, null, null,
50
- function(filename, type, baseFolder) {
166
+ // Main test runs using glob patterns (cosmiconfig handles configs)
167
+ {
168
+ patterns: globPatterns
169
+ },
170
+
171
+ // Error tests
172
+ {
173
+ patterns: ['tests-error/eval/*.less'],
174
+ verifyFunction: lessTester.testErrors
175
+ },
176
+ {
177
+ patterns: ['tests-error/parse/*.less'],
178
+ verifyFunction: lessTester.testErrors
179
+ },
180
+
181
+ // Special test cases with specific handling
182
+ {
183
+ patterns: ['tests-config/js-type-errors/*.less'],
184
+ verifyFunction: lessTester.testTypeErrors
185
+ },
186
+ {
187
+ patterns: ['tests-config/no-js-errors/*.less'],
188
+ verifyFunction: lessTester.testErrors
189
+ },
190
+
191
+ // Sourcemap tests with special handling
192
+ {
193
+ patterns: [
194
+ 'tests-config/sourcemaps/**/*.less',
195
+ 'tests-config/sourcemaps-url/**/*.less',
196
+ 'tests-config/sourcemaps-rootpath/**/*.less',
197
+ 'tests-config/sourcemaps-basepath/**/*.less',
198
+ 'tests-config/sourcemaps-include-source/**/*.less'
199
+ ],
200
+ verifyFunction: lessTester.testSourcemap,
201
+ getFilename: function(filename, type, baseFolder) {
51
202
  if (type === 'vars') {
52
203
  return path.join(baseFolder, filename) + '.json';
53
204
  }
54
- return path.join('test/sourcemaps', filename) + '.json';
55
- }],
56
-
57
- [{math: 'strict', strictUnits: true, globalVars: true }, '_main/import/json/',
58
- lessTester.testImports, null, true,
59
- function(filename, type, baseFolder) {
60
- return path.join(baseFolder, filename) + '.json';
61
- }],
62
- [{math: 'strict', strictUnits: true, sourceMap: {sourceMapFileInline: true}},
63
- 'sourcemaps-empty/', lessTester.testEmptySourcemap],
64
- [{math: 'strict', strictUnits: true, sourceMap: {disableSourcemapAnnotation: true}},
65
- 'sourcemaps-disable-annotation/', lessTester.testSourcemapWithoutUrlAnnotation],
66
- [{math: 'strict', strictUnits: true, sourceMap: true},
67
- 'sourcemaps-variable-selector/', lessTester.testSourcemapWithVariableInSelector],
68
- [{globalVars: true, banner: '/**\n * Test\n */\n'}, 'globalVars/',
69
- null, null, null, function(name, type, baseFolder) { return path.join(baseFolder, name) + '.json'; }],
70
- [{modifyVars: true}, 'modifyVars/',
71
- null, null, null, function(name, type, baseFolder) { return path.join(baseFolder, name) + '.json'; }],
72
- [{urlArgs: '424242'}, 'url-args/'],
73
- [{rewriteUrls: 'all'}, 'rewrite-urls-all/'],
74
- [{rewriteUrls: 'local'}, 'rewrite-urls-local/'],
75
- [{rootpath: 'http://example.com/assets/css/', rewriteUrls: 'all'}, 'rootpath-rewrite-urls-all/'],
76
- [{rootpath: 'http://example.com/assets/css/', rewriteUrls: 'local'}, 'rootpath-rewrite-urls-local/'],
77
- [{paths: ['data/', '_main/import/']}, 'include-path/'],
78
- [{paths: 'data/'}, 'include-path-string/'],
79
- [{plugin: 'test/plugins/postprocess/'}, 'postProcessorPlugin/'],
80
- [{plugin: 'test/plugins/preprocess/'}, 'preProcessorPlugin/'],
81
- [{plugin: 'test/plugins/visitor/'}, 'visitorPlugin/'],
82
- [{plugin: 'test/plugins/filemanager/'}, 'filemanagerPlugin/'],
83
- [{math: 0}, '3rd-party/'],
84
- [{ processImports: false }, 'process-imports/']
205
+ // Extract just the filename (without directory) for the JSON file
206
+ var jsonFilename = path.basename(filename);
207
+ // For sourcemap type, return path relative to test directory
208
+ if (type === 'sourcemap') {
209
+ return path.join('test/sourcemaps', jsonFilename) + '.json';
210
+ }
211
+ return path.join('test/sourcemaps', jsonFilename) + '.json';
212
+ }
213
+ },
214
+ {
215
+ patterns: ['tests-config/sourcemaps-empty/*.less'],
216
+ verifyFunction: lessTester.testEmptySourcemap
217
+ },
218
+ {
219
+ patterns: ['tests-config/sourcemaps-disable-annotation/*.less'],
220
+ verifyFunction: lessTester.testSourcemapWithoutUrlAnnotation
221
+ },
222
+ {
223
+ patterns: ['tests-config/sourcemaps-variable-selector/*.less'],
224
+ verifyFunction: lessTester.testSourcemapWithVariableInSelector
225
+ },
226
+
227
+ // Import tests with JSON configs
228
+ {
229
+ patterns: ['tests-config/globalVars/*.less'],
230
+ lessOptions: {
231
+ globalVars: function(file) {
232
+ const fs = require('fs');
233
+ const path = require('path');
234
+ const basename = path.basename(file, '.less');
235
+ const jsonPath = path.join(path.dirname(file), basename + '.json');
236
+ try {
237
+ return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
238
+ } catch (e) {
239
+ return {};
240
+ }
241
+ }
242
+ }
243
+ },
244
+ {
245
+ patterns: ['tests-config/modifyVars/*.less'],
246
+ lessOptions: {
247
+ modifyVars: function(file) {
248
+ const fs = require('fs');
249
+ const path = require('path');
250
+ const basename = path.basename(file, '.less');
251
+ const jsonPath = path.join(path.dirname(file), basename + '.json');
252
+ try {
253
+ return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
254
+ } catch (e) {
255
+ return {};
256
+ }
257
+ }
258
+ }
259
+ }
85
260
  ];
86
- testMap.forEach(function(args) {
87
- lessTester.runTestSet.apply(lessTester, args)
261
+
262
+ // Note: needle mocking is set up globally at the top of the file
263
+
264
+ testMap.forEach(function(testConfig) {
265
+ // For glob patterns, pass lessOptions as the first parameter and patterns as the second
266
+ if (testConfig.patterns) {
267
+ lessTester.runTestSet(
268
+ testConfig.lessOptions || {}, // First param: options (including lessOptions)
269
+ testConfig.patterns, // Second param: patterns
270
+ testConfig.verifyFunction || null, // Third param: verifyFunction
271
+ testConfig.nameModifier || null, // Fourth param: nameModifier
272
+ testConfig.doReplacements || null, // Fifth param: doReplacements
273
+ testConfig.getFilename || null // Sixth param: getFilename
274
+ );
275
+ } else {
276
+ // Legacy format for non-glob tests
277
+ var args = [
278
+ testConfig.options || {}, // First param: options
279
+ testConfig.foldername, // Second param: foldername
280
+ testConfig.verifyFunction || null, // Third param: verifyFunction
281
+ testConfig.nameModifier || null, // Fourth param: nameModifier
282
+ testConfig.doReplacements || null, // Fifth param: doReplacements
283
+ testConfig.getFilename || null // Sixth param: getFilename
284
+ ];
285
+ lessTester.runTestSet.apply(lessTester, args);
286
+ }
88
287
  });
89
- lessTester.testSyncronous({syncImport: true}, '_main/import');
90
- lessTester.testSyncronous({syncImport: true}, '_main/plugin');
91
- lessTester.testSyncronous({syncImport: true}, 'math/strict/css');
288
+
289
+ // Special synchronous tests
290
+ lessTester.testSyncronous({syncImport: true}, 'tests-unit/import/import');
291
+ lessTester.testSyncronous({syncImport: true}, 'tests-config/math-strict/css');
292
+
92
293
  lessTester.testNoOptions();
93
294
  lessTester.testDisablePluginRule();
94
295
  lessTester.testJSImport();
95
296
  lessTester.finished();
96
297
 
97
- (() => {
98
- // Create new tester, since tests are not independent and tests
99
- // above modify tester in a way that breaks remote imports.
100
- lessTester = lessTest();
101
- var scope = nock('https://example.com')
102
- .get('/redirect.less').query(true)
103
- .reply(301, null, { location: '/target.less' })
104
- .get('/target.less').query(true)
105
- .reply(200);
106
- lessTester.runTestSet(
107
- {},
108
- 'import-redirect/',
109
- lessTester.testImportRedirect(scope)
110
- );
111
- lessTester.finished();
112
- })();
298
+
299
+ // Test HTTP redirect functionality
300
+ console.log('\nTesting HTTP redirect functionality...');
301
+ testHttpRedirects();
302
+ console.log('HTTP redirect test completed');
303
+
304
+ // Test import-remote functionality in isolation
305
+ console.log('\nTesting import-remote functionality...');
306
+ testImportRemote();
307
+ console.log('Import-remote test completed');