fix: prevent asset conflicts between React and Grid.js versions

Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -0,0 +1,6 @@
import { Csp } from '../csp';
import { Finding } from '../finding';
export declare function evaluateForFailure(parsedCsps: Csp[]): Finding[];
export declare function evaluateForWarnings(parsedCsps: Csp[]): Finding[];
export declare function evaluateForSyntaxErrors(parsedCsps: Csp[]): Finding[][];
//# sourceMappingURL=lighthouse_checks.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"lighthouse_checks.d.ts","sourceRoot":"","sources":["../../lighthouse/lighthouse_checks.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,GAAG,EAAqB,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAC;AA2EnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAsB/D;AAOD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAShE;AAOD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,CAYtE"}

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluateForSyntaxErrors = exports.evaluateForWarnings = exports.evaluateForFailure = void 0;
const parser_checks_1 = require("../checks/parser_checks");
const security_checks_1 = require("../checks/security_checks");
const strictcsp_checks_1 = require("../checks/strictcsp_checks");
const csp_1 = require("../csp");
function arrayContains(arr, elem) {
return arr.some(e => e.equals(elem));
}
function setIntersection(sets) {
const intersection = [];
if (sets.length === 0) {
return intersection;
}
const firstSet = sets[0];
for (const elem of firstSet) {
if (sets.every(set => arrayContains(set, elem))) {
intersection.push(elem);
}
}
return intersection;
}
function setUnion(sets) {
const union = [];
for (const set of sets) {
for (const elem of set) {
if (!arrayContains(union, elem)) {
union.push(elem);
}
}
}
return union;
}
function atLeastOnePasses(parsedCsps, checker) {
const findings = [];
for (const parsedCsp of parsedCsps) {
findings.push(checker(parsedCsp));
}
return setIntersection(findings);
}
function atLeastOneFails(parsedCsps, checker) {
const findings = [];
for (const parsedCsp of parsedCsps) {
findings.push(checker(parsedCsp));
}
return setUnion(findings);
}
function evaluateForFailure(parsedCsps) {
const targetsXssFindings = [
...atLeastOnePasses(parsedCsps, security_checks_1.checkMissingScriptSrcDirective),
...atLeastOnePasses(parsedCsps, security_checks_1.checkMissingObjectSrcDirective),
...security_checks_1.checkMultipleMissingBaseUriDirective(parsedCsps),
];
const effectiveCsps = parsedCsps.map(csp => csp.getEffectiveCsp(csp_1.Version.CSP3));
const effectiveCspsWithScript = effectiveCsps.filter(csp => {
const directiveName = csp.getEffectiveDirective(csp_1.Directive.SCRIPT_SRC);
return csp.directives[directiveName];
});
const robust = [
...atLeastOnePasses(effectiveCspsWithScript, strictcsp_checks_1.checkStrictDynamic),
...atLeastOnePasses(effectiveCspsWithScript, security_checks_1.checkScriptUnsafeInline),
...atLeastOnePasses(effectiveCsps, security_checks_1.checkWildcards),
...atLeastOnePasses(effectiveCsps, security_checks_1.checkPlainUrlSchemes),
];
return [...targetsXssFindings, ...robust];
}
exports.evaluateForFailure = evaluateForFailure;
function evaluateForWarnings(parsedCsps) {
return [
...atLeastOneFails(parsedCsps, strictcsp_checks_1.checkUnsafeInlineFallback),
...atLeastOneFails(parsedCsps, strictcsp_checks_1.checkAllowlistFallback)
];
}
exports.evaluateForWarnings = evaluateForWarnings;
function evaluateForSyntaxErrors(parsedCsps) {
const allFindings = [];
for (const csp of parsedCsps) {
const findings = [
...security_checks_1.checkNonceLength(csp), ...parser_checks_1.checkUnknownDirective(csp),
...security_checks_1.checkDeprecatedDirective(csp), ...parser_checks_1.checkMissingSemicolon(csp),
...parser_checks_1.checkInvalidKeyword(csp)
];
allFindings.push(findings);
}
return allFindings;
}
exports.evaluateForSyntaxErrors = evaluateForSyntaxErrors;
//# sourceMappingURL=lighthouse_checks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"lighthouse_checks.js","sourceRoot":"","sources":["../../lighthouse/lighthouse_checks.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
import 'jasmine';
//# sourceMappingURL=lighthouse_checks_test.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"lighthouse_checks_test.d.ts","sourceRoot":"","sources":["../../lighthouse/lighthouse_checks_test.ts"],"names":[],"mappings":"AAIC,OAAO,SAAS,CAAC"}

View File

@@ -0,0 +1,403 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
require("jasmine");
const finding_1 = require("../finding");
const parser_1 = require("../parser");
const lighthouseChecks = __importStar(require("./lighthouse_checks"));
function parsePolicies(policies) {
return policies.map(p => (new parser_1.CspParser(p)).csp);
}
describe('Test evaluateForFailure', () => {
it('robust nonce-based policy', () => {
const test = 'script-src \'nonce-aaaaaaaaaa\'; object-src \'none\'; base-uri \'none\'';
const violations = lighthouseChecks.evaluateForFailure(parsePolicies([test]));
expect(violations.length).toBe(0);
});
it('robust hash-based policy', () => {
const test = 'script-src \'sha256-aaaaaaaaaa\'; object-src \'none\'';
const violations = lighthouseChecks.evaluateForFailure(parsePolicies([test]));
expect(violations.length).toBe(0);
});
it('policy not attempt', () => {
const test = 'block-all-mixed-content';
const violations = lighthouseChecks.evaluateForFailure(parsePolicies([test]));
expect(violations.length).toBe(2);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description).toBe('script-src directive is missing.');
expect(violations[1].severity).toBe(finding_1.Severity.HIGH);
expect(violations[1].directive).toBe('object-src');
expect(violations[1].description)
.toBe(`Missing object-src allows the injection of plugins which can execute JavaScript. Can you set it to 'none'?`);
});
it('policy not robust', () => {
const test = 'script-src *.google.com; object-src \'none\'';
const violations = lighthouseChecks.evaluateForFailure(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe(`Host allowlists can frequently be bypassed. Consider using 'strict-dynamic' in combination with CSP nonces or hashes.`);
});
it('robust policy and not robust policy', () => {
const policies = [
'script-src *.google.com; object-src \'none\'',
'script-src \'nonce-aaaaaaaaaa\'; base-uri \'none\''
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('split across many policies', () => {
const policies = [
'object-src \'none\'', 'script-src \'nonce-aaaaaaaaaa\'',
'base-uri \'none\''
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('split across many policies with default-src', () => {
const policies = ['default-src \'none\'', 'base-uri \'none\''];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('split across many policies some mixed useless policies', () => {
const policies = [
'object-src \'none\'', 'script-src \'nonce-aaaaaaaaaa\'',
'base-uri \'none\'', 'block-all-mixed-content'
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('split across many policies with allowlist', () => {
const policies = [
'object-src \'none\'', 'script-src \'nonce-aaaaaaaaaa\'',
'base-uri \'none\'', 'script-src *'
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('not robust and not attempt', () => {
const policies = ['block-all-mixed-content', 'script-src *.google.com'];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(2);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('object-src');
expect(violations[0].description)
.toBe(`Missing object-src allows the injection of plugins which can execute JavaScript. Can you set it to 'none'?`);
expect(violations[1].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[1].directive).toBe('script-src');
expect(violations[1].description)
.toBe(`Host allowlists can frequently be bypassed. Consider using \'strict-dynamic\' in combination with CSP nonces or hashes.`);
});
it('robust check only CSPs with script-src', () => {
const policies = ['script-src https://example.com', 'object-src \'none\''];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe(`Host allowlists can frequently be bypassed. Consider using \'strict-dynamic\' in combination with CSP nonces or hashes.`);
});
it('two not attempt', () => {
const policies = ['block-all-mixed-content', 'block-all-mixed-content'];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(2);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description).toBe('script-src directive is missing.');
expect(violations[1].severity).toBe(finding_1.Severity.HIGH);
expect(violations[1].directive).toBe('object-src');
expect(violations[1].description)
.toBe(`Missing object-src allows the injection of plugins which can execute JavaScript. Can you set it to 'none'?`);
});
it('two not attempt somewhat', () => {
const policies = [
'block-all-mixed-content; object-src \'none\'',
'block-all-mixed-content',
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description).toBe('script-src directive is missing.');
});
it('base-uri split across many policies', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaaa\'; object-src \'none\'',
'base-uri \'none\'',
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('base-uri not set', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaaa\'; object-src \'none\'',
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('base-uri');
expect(violations[0].description)
.toBe(`Missing base-uri allows the injection of base tags. They can be used to set the base URL for all relative (script) URLs to an attacker controlled domain. Can you set it to 'none' or 'self'?`);
});
it('base-uri not set in either policy', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaaa\'; object-src \'none\'',
'block-all-mixed-content'
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('base-uri');
});
it('check wildcards', () => {
const policies = ['script-src \'none\'; object-src *'];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('object-src');
expect(violations[0].description)
.toBe(`object-src should not allow '*' as source`);
});
it('check wildcards on multiple', () => {
const policies = ['script-src \'none\'; object-src *', 'object-src \'none\''];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('check plain url schemes', () => {
const policies = [
`script-src 'strict-dynamic' 'nonce-random123' 'unsafe-inline' https:; base-uri 'none'; object-src https:`
];
const violations = lighthouseChecks.evaluateForFailure(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.HIGH);
expect(violations[0].directive).toBe('object-src');
expect(violations[0].description)
.toBe(`https: URI in object-src allows the execution of unsafe scripts.`);
});
});
describe('Test evaluateForWarnings', () => {
it('perfect', () => {
const test = 'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(0);
});
it('perfect except some failures', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url; object-src \'none\'',
'block-all-mixed-content'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('a perfect policy and a policy that does not target', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url; base-uri \'none\'; object-src \'none\'',
'block-all-mixed-content'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('perfect policy split into two', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url; base-uri \'none\'; ',
'block-all-mixed-content; object-src \'none\''
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('perfect policy split into three', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url; base-uri \'none\'; ',
'block-all-mixed-content', 'object-src \'none\''
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('no reporting and malformed', () => {
const test = 'script-src \'nonce-aaaaaaaaaa\'; unknown-directive';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe('Consider adding \'unsafe-inline\' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers.');
});
it('missing unsafe-inline fallback', () => {
const test = 'script-src \'nonce-aaaaaaaaaa\'; report-uri url';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe('Consider adding \'unsafe-inline\' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers.');
});
it('missing allowlist fallback', () => {
const test = 'script-src \'nonce-aaaaaaaaaa\' \'strict-dynamic\' \'unsafe-inline\'; report-uri url';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe('Consider adding https: and http: url schemes (ignored by browsers supporting \'strict-dynamic\') to be backward compatible with older browsers.');
});
it('missing semicolon', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'unsafe-inline\'; report-uri url object-src \'self\'';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(0);
});
it('invalid keyword', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'invalid\' \'unsafe-inline\'; report-uri url';
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies([test]));
expect(violations.length).toBe(0);
});
it('perfect policy and invalid policy', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:; report-uri url; base-uri \'none\'; object-src \'none\'',
'unknown'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('reporting on the wrong policy', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:',
'block-all-mixed-content; report-uri url'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(0);
});
it('missing unsafe-inline fallback split over two policies', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\'',
'block-all-mixed-content; report-uri url'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(1);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe('Consider adding \'unsafe-inline\' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers.');
});
it('strict-dynamic with no fallback in any policy', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'strict-dynamic\'',
'block-all-mixed-content; report-uri url'
];
const violations = lighthouseChecks.evaluateForWarnings(parsePolicies(policies));
expect(violations.length).toBe(2);
expect(violations[0].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[0].directive).toBe('script-src');
expect(violations[0].description)
.toBe('Consider adding \'unsafe-inline\' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers.');
expect(violations[1].severity).toBe(finding_1.Severity.STRICT_CSP);
expect(violations[1].directive).toBe('script-src');
expect(violations[1].description)
.toBe('Consider adding https: and http: url schemes (ignored by browsers supporting \'strict-dynamic\') to be backward compatible with older browsers.');
});
});
describe('Test evaluateForSyntaxErrors', () => {
it('whenPerfectPolicies', () => {
const policies = [
'script-src \'nonce-aaaaaaaaaa\' \'unsafe-inline\' http: https:',
'block-all-mixed-content; report-uri url'
];
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies(policies));
expect(violations.length).toBe(2);
expect(violations[0].length).toBe(0);
expect(violations[1].length).toBe(0);
});
it('whenShortNonce', () => {
const test = 'script-src \'nonce-a\' \'unsafe-inline\'; report-uri url';
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.MEDIUM);
expect(violations[0][0].directive).toBe('script-src');
expect(violations[0][0].description)
.toBe('Nonces should be at least 8 characters long.');
});
it('whenUnknownDirective', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'unsafe-inline\'; report-uri url; unknown';
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.SYNTAX);
expect(violations[0][0].directive).toBe('unknown');
expect(violations[0][0].description)
.toBe('Directive "unknown" is not a known CSP directive.');
});
it('whenDeprecatedDirective', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'unsafe-inline\'; report-uri url; reflected-xss foo';
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.INFO);
expect(violations[0][0].directive).toBe('reflected-xss');
expect(violations[0][0].description)
.toBe('reflected-xss is deprecated since CSP2. Please, use the X-XSS-Protection header instead.');
});
it('whenMissingSemicolon', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'unsafe-inline\'; report-uri url object-src \'none\'';
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.SYNTAX);
expect(violations[0][0].directive).toBe('report-uri');
expect(violations[0][0].description)
.toBe('Did you forget the semicolon? "object-src" seems to be a directive, not a value.');
});
it('whenInvalidKeyword', () => {
const test = 'script-src \'nonce-aaaaaaaaa\' \'unsafe-inline\'; object-src \'invalid\'';
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies([test]));
expect(violations.length).toBe(1);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.SYNTAX);
expect(violations[0][0].directive).toBe('object-src');
expect(violations[0][0].description)
.toBe('\'invalid\' seems to be an invalid CSP keyword.');
});
it('manyPolicies', () => {
const policies = [
'object-src \'invalid\'', 'script-src \'none\'',
'script-src \'nonce-short\' default-src \'none\''
];
const violations = lighthouseChecks.evaluateForSyntaxErrors(parsePolicies(policies));
expect(violations.length).toBe(3);
expect(violations[0].length).toBe(1);
expect(violations[0][0].severity).toBe(finding_1.Severity.SYNTAX);
expect(violations[0][0].directive).toBe('object-src');
expect(violations[0][0].description)
.toBe('\'invalid\' seems to be an invalid CSP keyword.');
expect(violations[1].length).toBe(0);
expect(violations[2].length).toBe(2);
expect(violations[2][0].severity).toBe(finding_1.Severity.MEDIUM);
expect(violations[2][0].directive).toBe('script-src');
expect(violations[2][0].description)
.toBe('Nonces should be at least 8 characters long.');
expect(violations[2][1].severity).toBe(finding_1.Severity.SYNTAX);
expect(violations[2][1].directive).toBe('script-src');
expect(violations[2][1].description)
.toBe('Did you forget the semicolon? "default-src" seems to be a directive, not a value.');
});
});
//# sourceMappingURL=lighthouse_checks_test.js.map

File diff suppressed because one or more lines are too long