@double-great/stylelint-a11y 3.1.0 → 3.2.0
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.
- package/CHANGELOG.md +34 -0
- package/README.md +24 -12
- package/package.json +10 -3
- package/src/rules/index.js +1 -1
- package/src/rules/media-prefers-reduced-motion/index.js +82 -19
- package/src/rules/no-obsolete-attribute/index.js +2 -0
- package/src/rules/no-obsolete-element/index.js +2 -0
- package/test/e2e/README.md +37 -0
- package/test/e2e/cli.test.js +71 -0
- package/test/e2e/performance.test.js +98 -0
- package/test/e2e/projects/basic-css/.stylelintrc.json +17 -0
- package/test/e2e/projects/basic-css/styles.css +81 -0
- package/test/e2e/projects/large-codebase/.stylelintrc.json +17 -0
- package/test/e2e/projects/large-codebase/components.css +329 -0
- package/test/e2e/projects/large-codebase/layout.css +393 -0
- package/test/e2e/projects/scss-project/.stylelintrc.json +18 -0
- package/test/e2e/projects/scss-project/main.scss +163 -0
- package/test/e2e/projects.test.js +349 -0
- package/test/helpers/exec-stylelint.js +80 -0
- package/test/helpers/simple-test-utils.js +75 -0
- package/test/integration/integration.test.js +245 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.2.0] - 2025-08-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Comprehensive E2E testing infrastructure with real-world projects
|
|
13
|
+
- Integration testing suite for plugin functionality
|
|
14
|
+
- Performance benchmarking with regression detection
|
|
15
|
+
- Test helper utilities for simplified testing
|
|
16
|
+
- Support for SCSS/Sass project testing
|
|
17
|
+
- Large codebase performance testing
|
|
18
|
+
- CLI integration tests with full stylelint compatibility
|
|
19
|
+
- Development documentation in README
|
|
20
|
+
- Pull request template for consistent PR descriptions
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Standardized import patterns across all rules for better consistency
|
|
25
|
+
- Fixed rule order in exports object to maintain alphabetical sorting
|
|
26
|
+
- Improved Jest configuration for multiple test types
|
|
27
|
+
- Enhanced ESLint configuration for E2E tests
|
|
28
|
+
- Updated actions/checkout from v4 to v5 (#77)
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Fixed `media-prefers-reduced-motion` rule incorrectly adding duplicate media queries when `prefers-reduced-motion` is already nested inside another media query (#66)
|
|
33
|
+
|
|
34
|
+
### Developer Experience
|
|
35
|
+
|
|
36
|
+
- Added npm scripts for granular test execution (`test:unit`, `test:integration`, `test:e2e`, `test:all`)
|
|
37
|
+
- Simplified test utilities for faster development
|
|
38
|
+
- Cross-platform compatibility testing
|
|
39
|
+
- Performance metrics and scaling tests
|
|
40
|
+
- Individual rule benchmarks included
|
|
41
|
+
|
|
8
42
|
## [3.1.0] - 2025-08-16
|
|
9
43
|
|
|
10
44
|
### Added
|
package/README.md
CHANGED
|
@@ -52,19 +52,31 @@ This shareable config contains the following:
|
|
|
52
52
|
|
|
53
53
|
Since it adds stylelint-a11y to `plugins`, you don't have to do this yourself when extending this config.
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Development
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
### Testing
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
- Improve documentation.
|
|
61
|
-
- Chime in on any open issue or pull request.
|
|
62
|
-
- Open new issues about your ideas on new rules, or for how to improve the existing ones, and pull requests to show us how your idea works.
|
|
63
|
-
- Add new tests to absolutely anything.
|
|
64
|
-
- Work on improving performance of rules.
|
|
65
|
-
- Contribute to [stylelint](https://github.com/stylelint/stylelint)
|
|
66
|
-
- Spread the word.
|
|
59
|
+
Run tests with the following commands:
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
- `npm run test` - Run unit tests for all rules
|
|
62
|
+
- `npm run test:unit` - Run unit tests only
|
|
63
|
+
- `npm run test:integration` - Run integration tests
|
|
64
|
+
- `npm run test:e2e` - Run end-to-end tests with real projects
|
|
65
|
+
- `npm run test:performance` - Run performance benchmarks
|
|
66
|
+
- `npm run test:all` - Run complete test suite
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
### Testing Infrastructure
|
|
69
|
+
|
|
70
|
+
This project includes testing at a few levels:
|
|
71
|
+
|
|
72
|
+
- **Unit tests** - Individual rule functionality
|
|
73
|
+
- **Integration tests** - Plugin integration with stylelint
|
|
74
|
+
- **E2E tests** - Real-world project testing with intentional violations
|
|
75
|
+
- **Performance tests** - Benchmark testing for large codebases
|
|
76
|
+
|
|
77
|
+
### Other Commands
|
|
78
|
+
|
|
79
|
+
- `npm run lint` - Run ESLint
|
|
80
|
+
- `npm run format:check` - Check code formatting
|
|
81
|
+
- `npm run format:fix` - Fix code formatting
|
|
82
|
+
- `npm run coverage` - Run tests with coverage report
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@double-great/stylelint-a11y",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Plugin for stylelint with a11y rules",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,14 @@
|
|
|
28
28
|
"pretest": "npm run lint && npm run format:check",
|
|
29
29
|
"format:check": "prettier --check .",
|
|
30
30
|
"format:fix": "prettier --write .",
|
|
31
|
-
"test": "jest",
|
|
32
|
-
"
|
|
31
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
32
|
+
"test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js src",
|
|
33
|
+
"test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration",
|
|
34
|
+
"test:e2e": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/e2e",
|
|
35
|
+
"test:performance": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/e2e/performance.test.js",
|
|
36
|
+
"test:all": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
37
|
+
"coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
38
|
+
"coverage:all": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
|
|
33
39
|
},
|
|
34
40
|
"prettier": {
|
|
35
41
|
"printWidth": 100,
|
|
@@ -48,6 +54,7 @@
|
|
|
48
54
|
"jest": "^30.0.5",
|
|
49
55
|
"jest-light-runner": "^0.7.9",
|
|
50
56
|
"jest-preset-stylelint": "^8.0.0",
|
|
57
|
+
"postcss-scss": "^4.0.9",
|
|
51
58
|
"prettier": "^3.6.2",
|
|
52
59
|
"stylelint": "^16.23.1"
|
|
53
60
|
},
|
package/src/rules/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export default {
|
|
|
15
15
|
'content-property-no-static-value': contentPropertyNoStaticValue,
|
|
16
16
|
'font-size-is-readable': fontSizeIsReadable,
|
|
17
17
|
'line-height-is-vertical-rhythmed': lineHeightIsVerticalRhythmed,
|
|
18
|
+
'media-prefers-color-scheme': mediaPrefersColorScheme,
|
|
18
19
|
'media-prefers-reduced-motion': mediaPrefersReducedMotion,
|
|
19
20
|
'no-display-none': noDisplayNone,
|
|
20
21
|
'no-obsolete-attribute': noObsoleteAttribute,
|
|
@@ -23,5 +24,4 @@ export default {
|
|
|
23
24
|
'no-spread-text': noSpreadText,
|
|
24
25
|
'no-text-align-justify': noTextAlignJustify,
|
|
25
26
|
'selector-pseudo-class-focus': selectorPseudoClassFocus,
|
|
26
|
-
'media-prefers-color-scheme': mediaPrefersColorScheme,
|
|
27
27
|
};
|
|
@@ -2,7 +2,9 @@ import isCustomSelector from 'stylelint/lib/utils/isCustomSelector.mjs';
|
|
|
2
2
|
import isStandardSyntaxAtRule from 'stylelint/lib/utils/isStandardSyntaxAtRule.mjs';
|
|
3
3
|
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule.mjs';
|
|
4
4
|
import isStandardSyntaxSelector from 'stylelint/lib/utils/isStandardSyntaxSelector.mjs';
|
|
5
|
+
|
|
5
6
|
import { parse } from 'postcss';
|
|
7
|
+
|
|
6
8
|
import stylelint from 'stylelint';
|
|
7
9
|
const {
|
|
8
10
|
utils: { report, ruleMessages, validateOptions },
|
|
@@ -62,6 +64,30 @@ function check(selector, node) {
|
|
|
62
64
|
|
|
63
65
|
if (!declarationsIsMatched) return true;
|
|
64
66
|
|
|
67
|
+
// Check if there's a nested media query with prefers-reduced-motion inside the current rule
|
|
68
|
+
const hasNestedPrefersReducedMotion = declarations.some((childNode) => {
|
|
69
|
+
if (childNode.type === 'atrule' && childNode.name === 'media') {
|
|
70
|
+
return childNode.params && childNode.params.indexOf('prefers-reduced-motion') >= 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (hasNestedPrefersReducedMotion) return true;
|
|
77
|
+
|
|
78
|
+
// Check if there's a sibling media query with prefers-reduced-motion at the same level
|
|
79
|
+
if (node.parent && node.parent.type === 'atrule' && node.parent.name === 'media') {
|
|
80
|
+
const siblingHasPrefersReducedMotion = node.parent.nodes.some((siblingNode) => {
|
|
81
|
+
if (siblingNode.type === 'atrule' && siblingNode.name === 'media') {
|
|
82
|
+
return siblingNode.params && siblingNode.params.indexOf('prefers-reduced-motion') >= 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (siblingHasPrefersReducedMotion) return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
65
91
|
if (declarationsIsMatched) {
|
|
66
92
|
const parentMatchedNode = parentNodes.some((parentNode) => {
|
|
67
93
|
if (!parentNode || !parentNode.nodes) return false;
|
|
@@ -143,25 +169,62 @@ export default function mediaPrefersReducedMotion(actual, _, context) {
|
|
|
143
169
|
media.nodes.forEach((o) => {
|
|
144
170
|
o.raws.after = '\n';
|
|
145
171
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
o.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
|
|
173
|
+
// Check if we're already inside a media query
|
|
174
|
+
if (node.parent && node.parent.type === 'atrule' && node.parent.name === 'media') {
|
|
175
|
+
// Create a clone with only the animation/transition properties set to none
|
|
176
|
+
const cloneRule = node.clone();
|
|
177
|
+
|
|
178
|
+
cloneRule.nodes = cloneRule.nodes.filter((o) => {
|
|
179
|
+
if (o.prop === 'animation-name') {
|
|
180
|
+
o.prop = 'animation';
|
|
181
|
+
o.value = 'none';
|
|
182
|
+
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (targetProperties.indexOf(o.prop) >= 0) {
|
|
187
|
+
o.value = 'none';
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
cloneRule.raws = {
|
|
196
|
+
...cloneRule.raws,
|
|
197
|
+
before: '\n',
|
|
198
|
+
after: '\n',
|
|
199
|
+
semicolon: true,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
media.first.append(cloneRule);
|
|
203
|
+
// Insert the nested media query inside the current rule
|
|
204
|
+
node.append(media.first);
|
|
205
|
+
} else {
|
|
206
|
+
// Original logic for non-nested case
|
|
207
|
+
const cloneRule = node.clone();
|
|
208
|
+
|
|
209
|
+
cloneRule.raws = {
|
|
210
|
+
...cloneRule.raws,
|
|
211
|
+
before: '\n',
|
|
212
|
+
after: '\n',
|
|
213
|
+
semicolon: true,
|
|
214
|
+
};
|
|
215
|
+
cloneRule.nodes.forEach((o) => {
|
|
216
|
+
if (o.prop === 'animation-name') {
|
|
217
|
+
o.prop = 'animation';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (targetProperties.indexOf(o.prop) >= 0) {
|
|
221
|
+
o.value = 'none';
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
media.first.append(cloneRule);
|
|
225
|
+
// Insert the media query before the current node
|
|
226
|
+
node.before(media);
|
|
227
|
+
}
|
|
165
228
|
|
|
166
229
|
return;
|
|
167
230
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# E2E Test Information
|
|
2
|
+
|
|
3
|
+
## About E2E Test "Failures"
|
|
4
|
+
|
|
5
|
+
The E2E tests in this directory are designed to test stylelint-a11y against real CSS files that **intentionally contain rule violations**.
|
|
6
|
+
|
|
7
|
+
### Expected Behavior
|
|
8
|
+
|
|
9
|
+
- Tests **expect** stylelint to exit with error codes when violations are found
|
|
10
|
+
- The tests catch these "failures" and parse the JSON output to verify:
|
|
11
|
+
- Correct number of violations detected
|
|
12
|
+
- Specific rules are triggered
|
|
13
|
+
- Line numbers and error messages are accurate
|
|
14
|
+
|
|
15
|
+
### Test Output
|
|
16
|
+
|
|
17
|
+
The console output may show:
|
|
18
|
+
|
|
19
|
+
- JSON arrays of violations (this is expected)
|
|
20
|
+
- Some error messages during test runs (also expected)
|
|
21
|
+
- Performance metrics from the performance tests
|
|
22
|
+
|
|
23
|
+
### In CI/GitHub Actions
|
|
24
|
+
|
|
25
|
+
These tests will pass in CI because:
|
|
26
|
+
|
|
27
|
+
- Jest catches the expected exceptions
|
|
28
|
+
- Tests verify the violations are correctly detected
|
|
29
|
+
- The test suite itself passes even though stylelint "fails" on the test CSS
|
|
30
|
+
|
|
31
|
+
## Test Projects
|
|
32
|
+
|
|
33
|
+
- `basic-css/`: Tests core CSS functionality
|
|
34
|
+
- `scss-project/`: Tests SCSS/Sass compatibility
|
|
35
|
+
- `large-codebase/`: Tests performance with larger files
|
|
36
|
+
|
|
37
|
+
All test projects contain intentional accessibility violations for testing purposes.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
describe('CLI Integration Tests', () => {
|
|
10
|
+
const projectRoot = join(__dirname, '../..');
|
|
11
|
+
|
|
12
|
+
it('should work with recommended config via CLI', async () => {
|
|
13
|
+
// Create a minimal test to verify CLI works with the plugin
|
|
14
|
+
const testCssFile = join(projectRoot, 'test-cli-temp.css');
|
|
15
|
+
const testConfigFile = join(projectRoot, '.stylelintrc-cli-temp.json');
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
await fs.writeFile(testCssFile, '.test:focus { outline: none; }');
|
|
19
|
+
await fs.writeFile(
|
|
20
|
+
testConfigFile,
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
plugins: ['./src/index.js'],
|
|
23
|
+
rules: { 'a11y/no-outline-none': true },
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
execSync(`npx stylelint "${testCssFile}" --config "${testConfigFile}"`, {
|
|
29
|
+
cwd: projectRoot,
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
throw new Error('Expected stylelint to exit with non-zero code');
|
|
34
|
+
} catch (error) {
|
|
35
|
+
expect(error.status).toBeGreaterThan(0);
|
|
36
|
+
}
|
|
37
|
+
} finally {
|
|
38
|
+
// Cleanup
|
|
39
|
+
await fs.unlink(testCssFile).catch(() => {});
|
|
40
|
+
await fs.unlink(testConfigFile).catch(() => {});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle valid CSS without CLI errors', async () => {
|
|
45
|
+
const testCssFile = join(projectRoot, 'test-cli-valid-temp.css');
|
|
46
|
+
const testConfigFile = join(projectRoot, '.stylelintrc-valid-temp.json');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await fs.writeFile(testCssFile, '.test:hover:focus { color: blue; }');
|
|
50
|
+
await fs.writeFile(
|
|
51
|
+
testConfigFile,
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
plugins: ['./src/index.js'],
|
|
54
|
+
rules: { 'a11y/selector-pseudo-class-focus': true },
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const result = execSync(`npx stylelint "${testCssFile}" --config "${testConfigFile}"`, {
|
|
59
|
+
cwd: projectRoot,
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Should succeed without errors
|
|
64
|
+
expect(typeof result).toBe('string');
|
|
65
|
+
} finally {
|
|
66
|
+
// Cleanup
|
|
67
|
+
await fs.unlink(testCssFile).catch(() => {});
|
|
68
|
+
await fs.unlink(testConfigFile).catch(() => {});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { testCSS, getWarnings } from '../helpers/simple-test-utils.js';
|
|
2
|
+
|
|
3
|
+
describe('Performance Tests', () => {
|
|
4
|
+
it('should process rules efficiently', async () => {
|
|
5
|
+
const testCss = `
|
|
6
|
+
.test1:focus { outline: none; }
|
|
7
|
+
.test2:hover { color: blue; }
|
|
8
|
+
.test3 { font-size: 10px; }
|
|
9
|
+
.test4::before { content: "text"; }
|
|
10
|
+
.test5 { line-height: 1.0; }
|
|
11
|
+
.test6 { text-align: justify; }
|
|
12
|
+
.test7 { animation: slide 1s; }
|
|
13
|
+
.test8 { display: none; }
|
|
14
|
+
`.repeat(50); // Create larger test case
|
|
15
|
+
|
|
16
|
+
const times = [];
|
|
17
|
+
|
|
18
|
+
// Run multiple iterations to get average performance
|
|
19
|
+
for (let i = 0; i < 3; i++) {
|
|
20
|
+
const startTime = process.hrtime.bigint();
|
|
21
|
+
|
|
22
|
+
await testCSS(testCss, {
|
|
23
|
+
'no-outline-none': true,
|
|
24
|
+
'selector-pseudo-class-focus': true,
|
|
25
|
+
'font-size-is-readable': true,
|
|
26
|
+
'content-property-no-static-value': true,
|
|
27
|
+
'line-height-is-vertical-rhythmed': true,
|
|
28
|
+
'no-text-align-justify': true,
|
|
29
|
+
'media-prefers-reduced-motion': true,
|
|
30
|
+
'no-display-none': true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const endTime = process.hrtime.bigint();
|
|
34
|
+
const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds
|
|
35
|
+
|
|
36
|
+
times.push(duration);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const averageTime = times.reduce((sum, time) => sum + time, 0) / times.length;
|
|
40
|
+
|
|
41
|
+
// Performance assertions
|
|
42
|
+
expect(averageTime).toBeLessThan(1000); // Average should be under 1 second
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should scale reasonably with CSS size', async () => {
|
|
46
|
+
const baseCss = '.test:focus { outline: none; } .test:hover { color: blue; }';
|
|
47
|
+
const sizes = [100, 500]; // Different repetition counts
|
|
48
|
+
const results = [];
|
|
49
|
+
|
|
50
|
+
for (const size of sizes) {
|
|
51
|
+
const testCss = baseCss.repeat(size);
|
|
52
|
+
|
|
53
|
+
const startTime = process.hrtime.bigint();
|
|
54
|
+
|
|
55
|
+
await testCSS(testCss, {
|
|
56
|
+
'no-outline-none': true,
|
|
57
|
+
'selector-pseudo-class-focus': true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const endTime = process.hrtime.bigint();
|
|
61
|
+
const duration = Number(endTime - startTime) / 1000000;
|
|
62
|
+
|
|
63
|
+
results.push({ size, duration });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check that performance scales reasonably
|
|
67
|
+
const scalingFactor = results[1].duration / results[0].duration; // 500 vs 100 rules
|
|
68
|
+
|
|
69
|
+
expect(scalingFactor).toBeLessThan(25); // Should not be more than 25x slower for 5x content
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle large stylesheets without memory issues', async () => {
|
|
73
|
+
let largeCss = '';
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < 100; i++) {
|
|
76
|
+
largeCss += `.component-${i} { font-size: 10px; color: red; }\n`;
|
|
77
|
+
largeCss += `.component-${i}:hover { color: blue; }\n`;
|
|
78
|
+
largeCss += `.component-${i}:focus { outline: none; }\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const memoryBefore = process.memoryUsage().heapUsed;
|
|
82
|
+
|
|
83
|
+
const warnings = await getWarnings(largeCss, {
|
|
84
|
+
'no-outline-none': true,
|
|
85
|
+
'font-size-is-readable': true,
|
|
86
|
+
'selector-pseudo-class-focus': true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const memoryAfter = process.memoryUsage().heapUsed;
|
|
90
|
+
const memoryDiff = memoryAfter - memoryBefore;
|
|
91
|
+
|
|
92
|
+
// Should detect violations
|
|
93
|
+
expect(warnings.length).toBeGreaterThan(0);
|
|
94
|
+
|
|
95
|
+
// Memory usage should be reasonable (less than 50MB increase)
|
|
96
|
+
expect(memoryDiff).toBeLessThan(50 * 1024 * 1024);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": ["../../../../src/index.js"],
|
|
3
|
+
"rules": {
|
|
4
|
+
"a11y/content-property-no-static-value": true,
|
|
5
|
+
"a11y/font-size-is-readable": true,
|
|
6
|
+
"a11y/line-height-is-vertical-rhythmed": true,
|
|
7
|
+
"a11y/media-prefers-color-scheme": true,
|
|
8
|
+
"a11y/media-prefers-reduced-motion": true,
|
|
9
|
+
"a11y/no-display-none": true,
|
|
10
|
+
"a11y/no-obsolete-attribute": true,
|
|
11
|
+
"a11y/no-obsolete-element": true,
|
|
12
|
+
"a11y/no-outline-none": true,
|
|
13
|
+
"a11y/no-spread-text": true,
|
|
14
|
+
"a11y/no-text-align-justify": true,
|
|
15
|
+
"a11y/selector-pseudo-class-focus": true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* Basic CSS project for E2E testing */
|
|
2
|
+
|
|
3
|
+
/* Navigation styles */
|
|
4
|
+
.nav {
|
|
5
|
+
background: #333;
|
|
6
|
+
padding: 1rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.nav a:hover {
|
|
10
|
+
color: #fff;
|
|
11
|
+
text-decoration: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.nav .active {
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Button styles */
|
|
19
|
+
.btn:focus {
|
|
20
|
+
outline: none; /* Should trigger a11y/no-outline-none */
|
|
21
|
+
background: #007bff;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.btn-primary {
|
|
25
|
+
background: #007bff;
|
|
26
|
+
color: white;
|
|
27
|
+
padding: 0.5rem 1rem;
|
|
28
|
+
border: none;
|
|
29
|
+
font-size: 12px; /* Should trigger a11y/font-size-is-readable */
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.btn-secondary:hover {
|
|
33
|
+
background: #6c757d; /* Should trigger a11y/selector-pseudo-class-focus */
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Content styles */
|
|
37
|
+
.content::before {
|
|
38
|
+
content: '★'; /* Should trigger a11y/content-property-no-static-value */
|
|
39
|
+
color: gold;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.article {
|
|
43
|
+
line-height: 1.2; /* Should trigger a11y/line-height-is-vertical-rhythmed */
|
|
44
|
+
text-align: justify; /* Should trigger a11y/no-text-align-justify */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Animations */
|
|
48
|
+
.slide-in {
|
|
49
|
+
animation: slideIn 0.5s ease; /* Should trigger a11y/media-prefers-reduced-motion */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes slideIn {
|
|
53
|
+
from {
|
|
54
|
+
transform: translateX(-100%);
|
|
55
|
+
}
|
|
56
|
+
to {
|
|
57
|
+
transform: translateX(0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Hidden elements */
|
|
62
|
+
.sr-only {
|
|
63
|
+
display: none; /* Should trigger a11y/no-display-none */
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Obsolete elements and attributes */
|
|
67
|
+
center {
|
|
68
|
+
margin: 0 auto; /* Should trigger a11y/no-obsolete-element */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.old-style {
|
|
72
|
+
text-decoration: blink; /* Should trigger a11y/no-obsolete-attribute */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Media queries */
|
|
76
|
+
@media screen and (max-width: 768px) {
|
|
77
|
+
.mobile-nav a:hover {
|
|
78
|
+
color: blue; /* Should trigger a11y/selector-pseudo-class-focus */
|
|
79
|
+
font-size: 10px; /* Should trigger a11y/font-size-is-readable */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": ["../../../../src/index.js"],
|
|
3
|
+
"rules": {
|
|
4
|
+
"a11y/content-property-no-static-value": true,
|
|
5
|
+
"a11y/font-size-is-readable": [true, { "minSize": 12 }],
|
|
6
|
+
"a11y/line-height-is-vertical-rhythmed": true,
|
|
7
|
+
"a11y/media-prefers-color-scheme": true,
|
|
8
|
+
"a11y/media-prefers-reduced-motion": true,
|
|
9
|
+
"a11y/no-display-none": true,
|
|
10
|
+
"a11y/no-obsolete-attribute": true,
|
|
11
|
+
"a11y/no-obsolete-element": true,
|
|
12
|
+
"a11y/no-outline-none": true,
|
|
13
|
+
"a11y/no-spread-text": true,
|
|
14
|
+
"a11y/no-text-align-justify": true,
|
|
15
|
+
"a11y/selector-pseudo-class-focus": true
|
|
16
|
+
}
|
|
17
|
+
}
|