testaro 26.5.0 → 27.0.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.
- package/CONTRIBUTING.md +10 -9
- package/package.json +1 -1
- package/procs/testaro.js +40 -2
- package/testaro/allCaps.js +17 -17
- package/testaro/allSlanted.js +17 -20
- package/testaro/distortion.js +17 -14
- package/testaro/filter.js +17 -17
- package/testaro/linkTitle.js +22 -21
- package/testaro/linkUl.js +28 -23
- package/testaro/nonTable.js +19 -17
- package/validation/tests/jobs/adbID.json +0 -10
- package/validation/tests/jobs/linkTitle.json +5 -0
- package/validation/tests/jobs/linkUl.json +57 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -82,15 +82,16 @@ You can copy and revise any of the existing JSON files in the `testaro` director
|
|
|
82
82
|
- Assign an integer from 0 through 3 to `ordinalSeverity`.
|
|
83
83
|
- If all instances of violations of the rule necessarily involve elements of the same type, assign its tag name (such as `"BUTTON"`) to `summaryTagName`.
|
|
84
84
|
|
|
85
|
+
### Simplifiable rules
|
|
86
|
+
|
|
87
|
+
More complex Testaro rules are implemented in JavaScript. Some rules are _simplifiable_. These can be implemented with JavaScript modules like the one for the `allSlanted` rule. To implement such a rule, you can copy an existing module and replace the 7 values of the `ruleData` object. The significant decisions here are about the values of the `selector` and `pruner` properties.
|
|
88
|
+
|
|
89
|
+
The `selector` value is a CSS selector that identifies candidate elements for violation reporting. What makes this rule simplifiable, instead of simple, is that these elements may or may not be determined to violate the rule. Each of the elements identified by the selector must be further analyzed by the pruner. The pruner takes a Playwright locator as its argument and returns `true` if it finds that the element located by the locator violates the rule, or `false` if not.
|
|
90
|
+
|
|
91
|
+
The `isDestructive` property should be set to `true` if your pruner modifies the page. Any pruner that calls the `isOperable()` function from the `operable` module does so.
|
|
92
|
+
|
|
85
93
|
### Complex rules
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
- `allSlanted`, `distortion`, `filter`, `lineHeight`, `miniText`, `zIndex`: violations based on element styles.
|
|
89
|
-
- `focOp`, `opFoc`, `targetSize`: violations based on attributes
|
|
90
|
-
- `linkTitle`: violations based on attributes and text content.
|
|
91
|
-
- `linkAmb`, `pseudoP`, `radioSet`: violations based on relations among elements and text.
|
|
92
|
-
- `hover`, `tabNav`: violations based on performing actions and observing page behavior.
|
|
93
|
-
- `role`: violations based on data about standards.
|
|
94
|
-
- `docType`, `title`: violations based on page properties.
|
|
95
|
+
Even more complex Testaro rules require analysis that cannot fit into the simple or simplifiable category. You can begin with existing JavaScript rules, or the `data/template.js` file, as an example.
|
|
95
96
|
|
|
96
|
-
Some utility functions in modules in the `procs` directory are available for support of new rules.
|
|
97
|
+
Some utility functions in modules in the `procs` directory are available for support of new rules. Among these modules are `testaro` (used in many tests), `isInlineLink`, `operable`, and `visChange`,
|
package/package.json
CHANGED
package/procs/testaro.js
CHANGED
|
@@ -14,7 +14,7 @@ const sampleMax = 100;
|
|
|
14
14
|
// ########## FUNCTIONS
|
|
15
15
|
|
|
16
16
|
// Initializes locators and a result.
|
|
17
|
-
|
|
17
|
+
const init = async (page, locAllSelector, options = {}) => {
|
|
18
18
|
// Get locators for the specified elements.
|
|
19
19
|
const locPop = page.locator(locAllSelector, options);
|
|
20
20
|
const locPops = await locPop.all();
|
|
@@ -40,7 +40,7 @@ exports.init = async (page, locAllSelector, options = {}) => {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
// Populates a result.
|
|
43
|
-
|
|
43
|
+
const report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName = '') => {
|
|
44
44
|
const {locs, result} = all;
|
|
45
45
|
const {data, totals, standardInstances} = result;
|
|
46
46
|
// For each instance locator:
|
|
@@ -92,3 +92,41 @@ exports.report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName
|
|
|
92
92
|
// Return the result.
|
|
93
93
|
return result;
|
|
94
94
|
};
|
|
95
|
+
// Performs a simplifiable test.
|
|
96
|
+
const simplify = async (page, withItems, ruleData) => {
|
|
97
|
+
const {
|
|
98
|
+
ruleID, selector, pruner, isDestructive, complaints, ordinalSeverity, summaryTagName
|
|
99
|
+
} = ruleData;
|
|
100
|
+
// Initialize the locators and result.
|
|
101
|
+
const all = await init(page, selector);
|
|
102
|
+
// For each locator:
|
|
103
|
+
for (const loc of all.allLocs) {
|
|
104
|
+
// Get whether its element violates the rule.
|
|
105
|
+
const isBad = await pruner(loc);
|
|
106
|
+
// If it does:
|
|
107
|
+
if (isBad) {
|
|
108
|
+
// Add the locator to the array of violators.
|
|
109
|
+
all.locs.push(loc);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Populate and return the result.
|
|
113
|
+
const whats = [
|
|
114
|
+
complaints.instance,
|
|
115
|
+
complaints.summary
|
|
116
|
+
];
|
|
117
|
+
const testReport = await report(withItems, all, ruleID, whats, ordinalSeverity, summaryTagName);
|
|
118
|
+
// If the pruner modifies the page:
|
|
119
|
+
if (isDestructive) {
|
|
120
|
+
// Reload the page.
|
|
121
|
+
try {
|
|
122
|
+
await page.reload({timeout: 15000});
|
|
123
|
+
}
|
|
124
|
+
catch(error) {
|
|
125
|
+
console.log('ERROR: page reload timed out');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return testReport;
|
|
129
|
+
};
|
|
130
|
+
exports.init = init;
|
|
131
|
+
exports.report = report;
|
|
132
|
+
exports.simplify = simplify;
|
package/testaro/allCaps.js
CHANGED
|
@@ -8,18 +8,17 @@
|
|
|
8
8
|
// ########## IMPORTS
|
|
9
9
|
|
|
10
10
|
// Module to perform common operations.
|
|
11
|
-
const {
|
|
11
|
+
const {simplify} = require('../procs/testaro');
|
|
12
12
|
|
|
13
13
|
// ########## FUNCTIONS
|
|
14
14
|
|
|
15
15
|
// Runs the test and returns the result.
|
|
16
16
|
exports.reporter = async (page, withItems) => {
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const isBad = await loc.evaluate(el => {
|
|
17
|
+
// Specify the rule.
|
|
18
|
+
const ruleData = {
|
|
19
|
+
ruleID: 'allCaps',
|
|
20
|
+
selector: 'body *:not(style, script, svg)',
|
|
21
|
+
pruner: async loc => await loc.evaluate(el => {
|
|
23
22
|
const elText = Array
|
|
24
23
|
.from(el.childNodes)
|
|
25
24
|
.filter(node => node.nodeType === Node.TEXT_NODE)
|
|
@@ -37,14 +36,15 @@ exports.reporter = async (page, withItems) => {
|
|
|
37
36
|
const transformStyle = elStyleDec.textTransform;
|
|
38
37
|
return transformStyle === 'uppercase' && elText.length > 7;
|
|
39
38
|
}
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
all
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
}),
|
|
40
|
+
isDestructive: false,
|
|
41
|
+
complaints: {
|
|
42
|
+
instance: 'Element contains all-capital text',
|
|
43
|
+
summary: 'Elements contain all-capital text'
|
|
44
|
+
},
|
|
45
|
+
ordinalSeverity: 0,
|
|
46
|
+
summaryTagName: ''
|
|
47
|
+
};
|
|
48
|
+
// Run the test and return the result.
|
|
49
|
+
return await simplify(page, withItems, ruleData);
|
|
50
50
|
};
|
package/testaro/allSlanted.js
CHANGED
|
@@ -8,32 +8,29 @@
|
|
|
8
8
|
// ########## IMPORTS
|
|
9
9
|
|
|
10
10
|
// Module to perform common operations.
|
|
11
|
-
const {
|
|
11
|
+
const {simplify} = require('../procs/testaro');
|
|
12
12
|
|
|
13
13
|
// ########## FUNCTIONS
|
|
14
14
|
|
|
15
15
|
// Runs the test and returns the result.
|
|
16
16
|
exports.reporter = async (page, withItems) => {
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const isBad = await loc.evaluate(el => {
|
|
17
|
+
// Specify the rule.
|
|
18
|
+
const ruleData = {
|
|
19
|
+
ruleID: 'allSlanted',
|
|
20
|
+
selector: 'body *:not(style, script, svg)',
|
|
21
|
+
pruner: async loc => await loc.evaluate(el => {
|
|
23
22
|
const elStyleDec = window.getComputedStyle(el);
|
|
24
23
|
const elText = el.textContent;
|
|
25
24
|
return ['italic', 'oblique'].includes(elStyleDec.fontStyle) && elText.length > 39;
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
all
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
];
|
|
38
|
-
return await report(withItems, all, 'allSlanted', whats, 0);
|
|
25
|
+
}),
|
|
26
|
+
isDestructive: false,
|
|
27
|
+
complaints: {
|
|
28
|
+
instance: 'Element contains all-italic or all-oblique text',
|
|
29
|
+
summary: 'Elements contain all-italic or all-oblique text'
|
|
30
|
+
},
|
|
31
|
+
ordinalSeverity: 0,
|
|
32
|
+
summaryTagName: ''
|
|
33
|
+
};
|
|
34
|
+
// Run the test and return the result.
|
|
35
|
+
return await simplify(page, withItems, ruleData);
|
|
39
36
|
};
|
package/testaro/distortion.js
CHANGED
|
@@ -8,27 +8,30 @@
|
|
|
8
8
|
// ########## IMPORTS
|
|
9
9
|
|
|
10
10
|
// Module to perform common operations.
|
|
11
|
-
const {
|
|
11
|
+
const {simplify} = require('../procs/testaro');
|
|
12
12
|
|
|
13
13
|
// ########## FUNCTIONS
|
|
14
14
|
|
|
15
15
|
// Runs the test and returns the result.
|
|
16
16
|
exports.reporter = async (page, withItems) => {
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
// Specify the rule.
|
|
18
|
+
const ruleData = {
|
|
19
|
+
ruleID: 'distortion',
|
|
20
|
+
selector: 'body *',
|
|
21
|
+
pruner: async loc => await loc.evaluate(el => {
|
|
22
22
|
const styleDec = window.getComputedStyle(el);
|
|
23
23
|
const {transform} = styleDec;
|
|
24
24
|
return transform
|
|
25
25
|
&& ['matrix', 'perspective', 'rotate', 'scale', 'skew'].some(key => transform.includes(key));
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
}),
|
|
27
|
+
isDestructive: false,
|
|
28
|
+
complaints: {
|
|
29
|
+
instance: 'Element distorts its text',
|
|
30
|
+
summary: 'Elements distort their texts'
|
|
31
|
+
},
|
|
32
|
+
ordinalSeverity: 1,
|
|
33
|
+
summaryTagName: ''
|
|
34
|
+
};
|
|
35
|
+
// Run the test and return the result.
|
|
36
|
+
return await simplify(page, withItems, ruleData);
|
|
34
37
|
};
|
package/testaro/filter.js
CHANGED
|
@@ -9,28 +9,28 @@
|
|
|
9
9
|
// ########## IMPORTS
|
|
10
10
|
|
|
11
11
|
// Module to perform common operations.
|
|
12
|
-
const {
|
|
12
|
+
const {simplify} = require('../procs/testaro');
|
|
13
13
|
|
|
14
14
|
// ########## FUNCTIONS
|
|
15
15
|
|
|
16
16
|
// Runs the test and returns the result.
|
|
17
17
|
exports.reporter = async (page, withItems) => {
|
|
18
|
-
//
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const isBad = await loc.evaluate(el => {
|
|
18
|
+
// Specify the rule.
|
|
19
|
+
const ruleData = {
|
|
20
|
+
ruleID: 'filter',
|
|
21
|
+
selector: 'body *',
|
|
22
|
+
pruner: async loc => await loc.evaluate(el => {
|
|
24
23
|
const styleDec = window.getComputedStyle(el);
|
|
25
24
|
return styleDec.filter !== 'none';
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
}),
|
|
26
|
+
isDestructive: false,
|
|
27
|
+
complaints: {
|
|
28
|
+
instance: 'Element has a filter style',
|
|
29
|
+
summary: 'Elements have filter styles'
|
|
30
|
+
},
|
|
31
|
+
ordinalSeverity: 2,
|
|
32
|
+
summaryTagName: ''
|
|
33
|
+
};
|
|
34
|
+
// Run the test and return the result.
|
|
35
|
+
return await simplify(page, withItems, ruleData);
|
|
36
36
|
};
|
package/testaro/linkTitle.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
This test reports links with title attributes whose values the link text contains.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// ########## IMPORTS
|
|
8
|
+
|
|
7
9
|
// Module to perform common operations.
|
|
8
|
-
const {
|
|
10
|
+
const {simplify} = require('../procs/testaro');
|
|
9
11
|
// Module to get locator data.
|
|
10
12
|
const {getLocatorData} = require('../procs/getLocatorData');
|
|
11
13
|
|
|
@@ -13,24 +15,23 @@ const {getLocatorData} = require('../procs/getLocatorData');
|
|
|
13
15
|
|
|
14
16
|
// Runs the test and returns the result.
|
|
15
17
|
exports.reporter = async (page, withItems) => {
|
|
16
|
-
//
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return await report(withItems, all, 'linkTitle', whats, 0);
|
|
18
|
+
// Specify the rule.
|
|
19
|
+
const ruleData = {
|
|
20
|
+
ruleID: 'linkTitle',
|
|
21
|
+
selector: 'a[title]',
|
|
22
|
+
pruner: async loc => {
|
|
23
|
+
const elData = await getLocatorData(loc);
|
|
24
|
+
const title = await loc.getAttribute('title');
|
|
25
|
+
return elData.excerpt.toLowerCase().includes(title.toLowerCase());
|
|
26
|
+
},
|
|
27
|
+
isDestructive: false,
|
|
28
|
+
complaints: {
|
|
29
|
+
instance: 'Link has a title attribute that repeats link text content',
|
|
30
|
+
summary: 'Links have title attributes that repeat link text contents'
|
|
31
|
+
},
|
|
32
|
+
ordinalSeverity: 0,
|
|
33
|
+
summaryTagName: 'A'
|
|
34
|
+
};
|
|
35
|
+
// Run the test and return the result.
|
|
36
|
+
return await simplify(page, withItems, ruleData);
|
|
36
37
|
};
|
package/testaro/linkUl.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
merely to discover which passages are links.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
// ########## IMPORTS
|
|
12
|
+
|
|
11
13
|
// Module to perform common operations.
|
|
12
|
-
const {
|
|
14
|
+
const {simplify} = require('../procs/testaro');
|
|
13
15
|
// Module to classify links.
|
|
14
16
|
const {isInlineLink} = require('../procs/isInlineLink');
|
|
15
17
|
|
|
@@ -17,27 +19,30 @@ const {isInlineLink} = require('../procs/isInlineLink');
|
|
|
17
19
|
|
|
18
20
|
// Runs the test and returns the result.
|
|
19
21
|
exports.reporter = async (page, withItems) => {
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (isInline) {
|
|
35
|
-
// Add the locator to the array of violators.
|
|
36
|
-
all.locs.push(loc);
|
|
22
|
+
// Specify the rule.
|
|
23
|
+
const ruleData = {
|
|
24
|
+
ruleID: 'linkUl',
|
|
25
|
+
selector: 'a',
|
|
26
|
+
pruner: async loc => {
|
|
27
|
+
// Get whether each link is underlined.
|
|
28
|
+
const isUnderlined = await loc.evaluate(el => {
|
|
29
|
+
const styleDec = window.getComputedStyle(el);
|
|
30
|
+
return styleDec.textDecorationLine === 'underline';
|
|
31
|
+
});
|
|
32
|
+
// If it is not:
|
|
33
|
+
if (! isUnderlined) {
|
|
34
|
+
// Return whether it is a violator.
|
|
35
|
+
return await isInlineLink(loc);
|
|
37
36
|
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
},
|
|
38
|
+
isDestructive: false,
|
|
39
|
+
complaints: {
|
|
40
|
+
instance: 'Link is inline but has no underline',
|
|
41
|
+
summary: 'Inline links are missing underlines'
|
|
42
|
+
},
|
|
43
|
+
ordinalSeverity: 1,
|
|
44
|
+
summaryTagName: 'A'
|
|
45
|
+
};
|
|
46
|
+
// Run the test and return the result.
|
|
47
|
+
return await simplify(page, withItems, ruleData);
|
|
43
48
|
};
|
package/testaro/nonTable.js
CHANGED
|
@@ -4,19 +4,20 @@
|
|
|
4
4
|
This test reports tables used for layout.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// ########## IMPORTS
|
|
8
|
+
|
|
7
9
|
// Module to perform common operations.
|
|
8
|
-
const {
|
|
10
|
+
const {simplify} = require('../procs/testaro');
|
|
9
11
|
|
|
10
12
|
// ########## FUNCTIONS
|
|
11
13
|
|
|
12
14
|
// Runs the test and returns the result.
|
|
13
15
|
exports.reporter = async (page, withItems) => {
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const isBad = await loc.evaluate(el => {
|
|
16
|
+
// Specify the rule.
|
|
17
|
+
const ruleData = {
|
|
18
|
+
ruleID: 'nonTable',
|
|
19
|
+
selector: 'table',
|
|
20
|
+
pruner: async loc => await loc.evaluate(el => {
|
|
20
21
|
const role = el.getAttribute('role');
|
|
21
22
|
// If it contains another table:
|
|
22
23
|
if (el.querySelector('table')) {
|
|
@@ -54,14 +55,15 @@ exports.reporter = async (page, withItems) => {
|
|
|
54
55
|
// Return misuse.
|
|
55
56
|
return true;
|
|
56
57
|
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
}),
|
|
59
|
+
isDestructive: false,
|
|
60
|
+
complaints: {
|
|
61
|
+
instance: 'Table is misused to arrange content',
|
|
62
|
+
summary: 'Tables are misused to arrange content'
|
|
63
|
+
},
|
|
64
|
+
ordinalSeverity: 2,
|
|
65
|
+
summaryTagName: 'TABLE'
|
|
66
|
+
};
|
|
67
|
+
// Run the test and return the result.
|
|
68
|
+
return await simplify(page, withItems, ruleData);
|
|
67
69
|
};
|
|
@@ -170,6 +170,63 @@
|
|
|
170
170
|
"y",
|
|
171
171
|
"linkUl"
|
|
172
172
|
]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"type": "test",
|
|
176
|
+
"which": "testaro",
|
|
177
|
+
"withItems": false,
|
|
178
|
+
"stopOnFail": true,
|
|
179
|
+
"expect": [
|
|
180
|
+
[
|
|
181
|
+
"standardResult.totals.1",
|
|
182
|
+
"=",
|
|
183
|
+
6
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
"standardResult.totals.2",
|
|
187
|
+
"=",
|
|
188
|
+
0
|
|
189
|
+
],
|
|
190
|
+
[
|
|
191
|
+
"standardResult.instances.length",
|
|
192
|
+
"=",
|
|
193
|
+
1
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
"standardResult.instances.0.ruleID",
|
|
197
|
+
"=",
|
|
198
|
+
"linkUl"
|
|
199
|
+
],
|
|
200
|
+
[
|
|
201
|
+
"standardResult.instances.0.what",
|
|
202
|
+
"i",
|
|
203
|
+
"missing"
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
"standardResult.instances.0.ordinalSeverity",
|
|
207
|
+
"=",
|
|
208
|
+
1
|
|
209
|
+
],
|
|
210
|
+
[
|
|
211
|
+
"standardResult.instances.0.count",
|
|
212
|
+
"=",
|
|
213
|
+
6
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
"standardResult.instances.0.tagName",
|
|
217
|
+
"=",
|
|
218
|
+
"A"
|
|
219
|
+
],
|
|
220
|
+
[
|
|
221
|
+
"standardResult.instances.0.location.doc",
|
|
222
|
+
"=",
|
|
223
|
+
""
|
|
224
|
+
]
|
|
225
|
+
],
|
|
226
|
+
"rules": [
|
|
227
|
+
"y",
|
|
228
|
+
"linkUl"
|
|
229
|
+
]
|
|
173
230
|
}
|
|
174
231
|
],
|
|
175
232
|
"sources": {
|