diff --git a/api/functions/commands.js b/api/functions/commands.js index a2a5d232..2d6e7d8c 100644 --- a/api/functions/commands.js +++ b/api/functions/commands.js @@ -16,7 +16,7 @@ const { const azure = require('azure-storage'); const slug = require('slug'); -exports.insertScanResult = (api, buildId, runId, data, buildDate) => { +exports.insertScanResult = async (api, buildId, runId, data, buildDate, url) => { const entGen = azure.TableUtilities.entityGenerator; let entity = { PartitionKey: entGen.String(api), @@ -24,7 +24,18 @@ exports.insertScanResult = (api, buildId, runId, data, buildDate) => { buildId: entGen.String(buildId), runId: entGen.String(runId), buildDate: entGen.DateTime(buildDate), + apiKey: entGen.String(api) }; + let entityRunIndexed = { + ...entity, + PartitionKey: entGen.String(`${api}-${runId}`), + }; + let entityUrlIndexed = { + ...entity, + PartitionKey: entGen.String(`${api}-${slug(url)}`), + }; + await insertEntity(TABLE.ScanResults, replaceProp(data, entityRunIndexed)); + await insertEntity(TABLE.ScanResults, replaceProp(data, entityUrlIndexed)); return insertEntity(TABLE.ScanResults, replaceProp(data, entity)); }; @@ -105,16 +116,28 @@ exports.addHTMLHintRulesForEachRun = (api, data) => { ); }; -exports.insertScanSummary = (api, buildId, runId, buildDate, data) => { +exports.insertScanSummary = async (api, buildId, runId, buildDate, data) => { var entGen = azure.TableUtilities.entityGenerator; // use Log tail pattern to get native sort from Table Storage - var entity = { + const entity = { PartitionKey: entGen.String(api), RowKey: entGen.String(getReversedTick()), buildId: entGen.String(buildId), runId: entGen.String(runId), buildDate: entGen.DateTime(buildDate), + scanResultVersion: entGen.Int32(2), + apiKey: entGen.String(api) + }; + const entityRunIndexed = { + ...entity, + PartitionKey: entGen.String(`${api}-${runId}`), }; + let entityUrlIndexed = { + ...entity, + PartitionKey: entGen.String(`${api}-${slug(data.url)}`), + }; + await insertEntity(TABLE.Scans, replaceProp(data, entityRunIndexed)); + await insertEntity(TABLE.Scans, replaceProp(data, entityUrlIndexed)); return insertEntity(TABLE.Scans, replaceProp(data, entity)); }; @@ -139,6 +162,17 @@ exports.removeAlertEmailAddress = (api, rowkey) => { return deleteEntity(TABLE.alertEmailAddresses, entity); }; +exports.addCustomHtmlRuleOptions = (api, data) => { + const entGen = azure.TableUtilities.entityGenerator; + return updateEntity( + TABLE.HtmlRulesCustomOptions, + replaceProp(data, { + PartitionKey: entGen.String(api), + RowKey: entGen.String(data.ruleId), + }) + ); +}; + exports.uploadLighthouseReport = (runId, lhr) => uploadBlob(BLOB.lhr, `${runId}.json`, JSON.stringify(lhr)); diff --git a/api/functions/consts.js b/api/functions/consts.js index bc62242f..2c9a2dee 100644 --- a/api/functions/consts.js +++ b/api/functions/consts.js @@ -14,7 +14,8 @@ exports.TABLE = { ScanResults: 'ScanResults', htmlhintrules: 'htmlhintrules', alertEmailAddresses: 'alertEmailAddresses', - UnscannableLinks: 'UnscannableLinks' + UnscannableLinks: 'UnscannableLinks', + HtmlRulesCustomOptions: 'HtmlRulesCustomOptions' }; // blob storage names cannot have uppercase or numbers diff --git a/api/functions/index.js b/api/functions/index.js index a986e309..0a822ec9 100644 --- a/api/functions/index.js +++ b/api/functions/index.js @@ -22,6 +22,7 @@ const { addHTMLHintRulesForEachRun, addAlertEmailAddresses, removeAlertEmailAddress, + addCustomHtmlRuleOptions, } = require('./commands'); const { getPersonalSummary, @@ -39,6 +40,7 @@ const { getAllScanSummaryFromUrl, getUnscannableLinks, compareScans, + getCustomHtmlRuleOptions, } = require('./queries'); const { newGuid, @@ -179,6 +181,14 @@ app.get('/unscannableLinks', async (req, res) => { res.json(await getUnscannableLinks()); }); +app.post('/config/getCustomHtmlRuleOptions/:api', async (req, res) => { + res.json(await getCustomHtmlRuleOptions(req.params.api, req.body.url)); +}); + +app.post('/config/addCustomHtmlRuleOptions/:api', async (req, res) => { + res.json(await addCustomHtmlRuleOptions(req.params.api, req.body)); +}); + app.post('/scanresult/:api/:buildId', async (req, res) => { const { badUrls, @@ -230,7 +240,6 @@ app.post('/scanresult/:api/:buildId', async (req, res) => { const buildId = req.params.buildId; const runId = newGuid(); const buildDate = new Date(); - const unscannableLinks = await getUnscannableLinks(); const uid = await getUserIdFromApiKey(apikey); if (!uid) { @@ -314,7 +323,8 @@ app.post('/scanresult/:api/:buildId', async (req, res) => { buildId, runId, brokenLinkData, - buildDate + buildDate, + url ); cb(data); }, { diff --git a/api/functions/queries.js b/api/functions/queries.js index 89fe8601..b0f46447 100644 --- a/api/functions/queries.js +++ b/api/functions/queries.js @@ -72,7 +72,14 @@ exports.getConfig = (api) => exports.getScanDetails = async (runId) => { const scan = await exports.getSummaryById(runId); - const filter = odata`PartitionKey eq ${scan.partitionKey} and src ge ${scan.url} and src le ${incrementString(scan.url)}`; + let filter; + + if (scan.scanResultVersion === 2) { + filter = `PartitionKey eq '${scan.partitionKey}-${slug(scan.url)}'`; + } else { + filter = odata`PartitionKey eq ${scan.partitionKey} and src ge ${scan.url} and src le ${incrementString(scan.url)}`; + } + const entity = new TableClient(azureUrl, TABLE.ScanResults, credential).listEntities({ queryOptions: { filter } }); @@ -208,7 +215,10 @@ exports.getAllPublicSummary = (showAll) => for await (const item of entity) { result.push(item); } - resolve(result) + + resolve(result.filter((value, index, self) => { + return self.findIndex(v => v.runId === value.runId) === index; + })) } else { // Top 500 scans in last 24 months var date = new Date(); @@ -218,24 +228,39 @@ exports.getAllPublicSummary = (showAll) => queryOptions: { filter: odata`isPrivate eq ${false} and buildDate gt datetime'${date.toISOString()}'` } }); const iterator = entity.byPage({ maxPageSize: parseInt(process.env.MAX_SCAN_SIZE) }); + let result = []; for await (const item of iterator) { - resolve(item) + result = item; break; } + + resolve(result.filter((value, index, self) => { + return self.findIndex(v => v.runId === value.runId) === index; + })) } }); exports.getSummaryById = (runId) => getRun(runId).then((doc) => new Promise(async (resolve) => { - const entity = new TableClient(azureUrl, TABLE.Scans, credential).listEntities({ - queryOptions: { filter: odata`PartitionKey eq ${doc.apikey} and runId eq ${doc.runId}` } - }); - let result = [] - for await (const item of entity) { - result.push(item); + const getSummary = async (filter) => { + const entity = new TableClient(azureUrl, TABLE.Scans, credential).listEntities({ + queryOptions: { filter } + }); + let result = [] + for await (const item of entity) { + result.push(item); + } + return result[0]; + }; + + let summary = await getSummary(`PartitionKey eq '${doc.apikey}-${doc.runId}'`); + + if (!summary) { + summary = await getSummary(odata`PartitionKey eq ${doc.apikey} and runId eq ${doc.runId}`); } - resolve(result[0] || {}) + + resolve(summary || {}); })); exports.getLatestSummaryFromUrlAndApi = (url, api) => @@ -272,19 +297,30 @@ exports.getAlertEmailAddressesFromTokenAndUrl = (api, url) => exports.getAllScanSummaryFromUrl = (url, api) => new Promise(async (resolve) => { - const entity = new TableClient(azureUrl, TABLE.Scans, credential).listEntities({ - queryOptions: { filter: odata`url eq ${url} and PartitionKey eq ${api}` } - }); - const iterator = entity.byPage({ maxPageSize: 10 }); - for await (const item of iterator) { - if (item[0]) { - const existing = await getExistingBrokenLinkCount(item[0].runId); - item[0].totalUniqueBrokenLinksExisting = existing; + const getSummary = async (filter) => { + const entity = new TableClient(azureUrl, TABLE.Scans, credential).listEntities({ + queryOptions: { filter } + }); + const iterator = entity.byPage({ maxPageSize: 10 }); + let result; + for await (const item of iterator) { + if (item[0]) { + const existing = await getExistingBrokenLinkCount(item[0].runId); + item[0].totalUniqueBrokenLinksExisting = existing; + } + result = item; + break; } - - resolve(item); - break; + return result; + }; + + let summary = await getSummary(`PartitionKey eq '${api}-${slug(url)}'`); + + if (!summary) { + summary = await getSummary(odata`url eq ${url} and PartitionKey eq ${api}`); } + + resolve(summary); }); exports.getUnscannableLinks = () => @@ -332,3 +368,15 @@ exports.compareScans = (api, url) => } resolve(isErrorUp) }); + +exports.getCustomHtmlRuleOptions = (api, url) => + new Promise(async (resolve) => { + const entity = new TableClient(azureUrl, TABLE.HtmlRulesCustomOptions, credential).listEntities({ + queryOptions: { filter: odata`PartitionKey eq ${api} and url eq ${url}` } + }); + let result = [] + for await (const item of entity) { + result.push(item); + } + resolve(result || []) + }) \ No newline at end of file diff --git a/docker/api.js b/docker/api.js index cfa2013f..b431d536 100644 --- a/docker/api.js +++ b/docker/api.js @@ -125,6 +125,20 @@ exports.getAllScanSummaryFromUrl = (api, url) => { ); }; +exports.getCustomHtmlRuleOptions = (api, url) => { + return fetch(`${endpoint}/api/config/getCustomHtmlRuleOptions/${api}`, { + method: "POST", + body: JSON.stringify({url}), + headers: { "Content-Type": "application/json" }, + }).then((res) => { + if (res) { + return res.ok ? res.json() : []; + } else { + throw Error("Failed to get custom html rule options"); + } + }); +}; + exports.htmlHintConfig = { "grammar-scrum-terms": true, "code-block-missing-language": true, diff --git a/docker/customHtmlRules.js b/docker/customHtmlRules.js index 43eedb7e..0c552d4e 100644 --- a/docker/customHtmlRules.js +++ b/docker/customHtmlRules.js @@ -1,7 +1,10 @@ +const { getCustomHtmlRuleOptions } = require("./api"); const HTMLHint = require("htmlhint").default; const findPhoneNumbersInText = require('libphonenumber-js').findPhoneNumbersInText; -exports.addCustomHtmlRule = () => { +exports.addCustomHtmlRule = async (apiToken, url) => { + const customRuleOptions = await getCustomHtmlRuleOptions(apiToken, url); + HTMLHint.addRule({ id: "code-block-missing-language", description: "Code blocks must contain a language specifier.", @@ -380,9 +383,15 @@ exports.addCustomHtmlRule = () => { parser.addListener("tagstart", (event) => { var tagName = event.tagName.toLowerCase(), mapAttrs = parser.getMapAttrs(event.attrs); + const ruleId = "detect-absolute-references-url-path-correctly"; if (tagName === "a") { if (mapAttrs["href"]) { - if (mapAttrs["href"].startsWith("https://ssw.com.au/rules")) { + // Check if custom options exist in this rule + let optionValue = ''; + if (customRuleOptions && customRuleOptions.length > 0 && customRuleOptions.filter(option => option.ruleId === ruleId).length > 0) { + optionValue = customRuleOptions.find(option => option.ruleId === ruleId).optionValue + } + if (mapAttrs["href"].startsWith(optionValue.length > 0 ? optionValue : "https://ssw.com.au/rules")) { if (!mapAttrs["href"].startsWith("/")) { reporter.warn( "URLs must be formatted to direct to a url path correctly.", @@ -433,17 +442,26 @@ exports.addCustomHtmlRule = () => { var self = this; parser.addListener("text", (event) => { - var spellings = [ - "a.k.a", - "A.K.A", - "AKA", - "e-mail", - "EMail", - "can not", - "web site", - "user name", - "task bar" - ]; + const ruleId = "common-spelling-mistakes"; + let optionValue = []; + // Check if custom options exist in this rule + if (customRuleOptions && customRuleOptions.length > 0 && customRuleOptions.filter(option => option.ruleId === ruleId).length > 0) { + optionValue = customRuleOptions.find(option => option.ruleId === ruleId).optionValue.split(','); + } + var spellings = + optionValue.length > 0 ? + optionValue : + [ + "a.k.a", + "A.K.A", + "AKA", + "e-mail", + "EMail", + "can not", + "web site", + "user name", + "task bar" + ]; if (event.raw) { const pageContent = event.raw; @@ -485,7 +503,12 @@ exports.addCustomHtmlRule = () => { init: function (parser, reporter) { const self = this; let isInCodeBlock = false; + let optionValue = ''; parser.addListener("tagstart", (event) => { + // Check if custom options exist in this rule + if (customRuleOptions && customRuleOptions.length > 0 && customRuleOptions.filter(option => option.ruleId === ruleId).length > 0) { + optionValue = customRuleOptions.find(option => option.ruleId === ruleId).optionValue + } const tagName = event.tagName.toLowerCase(); if (tagName === "code") { isInCodeBlock = true; @@ -498,7 +521,9 @@ exports.addCustomHtmlRule = () => { } }); parser.addListener("text", (event) => { - if (event.raw && event.lastEvent && findPhoneNumbersInText(event.raw, "AU").length) { + // Replace "." and "/" characters to avoid false positives when parsing phone numbers + const text = event.raw?.replace(/\.|\//g, "_"); + if (text && event.lastEvent && findPhoneNumbersInText(text, optionValue.length > 0 ? optionValue : 'AU').length) { const pageContent = event.lastEvent.raw; if (pageContent && event.lastEvent.tagName) { const tagName = event.lastEvent.tagName.toLowerCase(); diff --git a/docker/index.js b/docker/index.js index 9dcfe063..b72ef376 100644 --- a/docker/index.js +++ b/docker/index.js @@ -29,6 +29,7 @@ const { runArtilleryLoadTest } = require("./utils"); const { htmlHintConfig } = require("./api"); +const { addCustomHtmlRule } = require("./customHtmlRules"); const LIGHTHOUSEFOLDER = "./lhr.json"; const ARTILLERYFOLDER = "./artilleryOut.json"; @@ -230,9 +231,12 @@ const processAndUpload = async ( [atr, atrSummary] = readArtilleryReport(ARTILLERYFOLDER, writeLog); } - const { addCustomHtmlRule } = require("./customHtmlRules"); if (args.htmlhint) { - addCustomHtmlRule(); + if (args.token) { + await addCustomHtmlRule(args.token, args.url); + } else { + await addCustomHtmlRule(); + }; [htmlIssuesSummary, htmlIssues] = await runHtmlHint( args.url, results, diff --git a/docker/test/anchor-names-must-be-valid.spec.js b/docker/test/anchor-names-must-be-valid.spec.js index 4464b969..d738ac3c 100644 --- a/docker/test/anchor-names-must-be-valid.spec.js +++ b/docker/test/anchor-names-must-be-valid.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Anchor name starts with letter and contains no space should not result in an error', () => { const code = 'Anchor' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/code-block-missing-language.spec.js b/docker/test/code-block-missing-language.spec.js index 1f949331..b98be909 100644 --- a/docker/test/code-block-missing-language.spec.js +++ b/docker/test/code-block-missing-language.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Code block with data language specifier should not result in an error', () => { const code = '
Some Code' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/common-spelling-mistakes.spec.js b/docker/test/common-spelling-mistakes.spec.js index 5549b574..bd4bbd66 100644 --- a/docker/test/common-spelling-mistakes.spec.js +++ b/docker/test/common-spelling-mistakes.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruleId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruleId}`, () => { - addCustomHtmlRule(); it("terms used correctly should not result in an error", () => { const code = "
aka email cannot website username taskbar
"; const messages = HTMLHint.verify(code, ruleOptions); diff --git a/docker/test/detect-absolute-references-correctly.spec.js b/docker/test/detect-absolute-references-correctly.spec.js index dd35056f..a673f692 100644 --- a/docker/test/detect-absolute-references-correctly.spec.js +++ b/docker/test/detect-absolute-references-correctly.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruldId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it("Bad example should result in an error", () => { const code = ''; const messages = HTMLHint.verify(code, ruleOptions); diff --git a/docker/test/figure-must-use-the-right-code.spec.js b/docker/test/figure-must-use-the-right-code.spec.js index d67c3133..16313b7a 100644 --- a/docker/test/figure-must-use-the-right-code.spec.js +++ b/docker/test/figure-must-use-the-right-code.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruldId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it("Figures not wrapped in figcaption must result in an error", () => { const code = "Figure: Caption
"; const messages = HTMLHint.verify(code, ruleOptions); diff --git a/docker/test/font-tag-must-not-be-used.spec.js b/docker/test/font-tag-must-not-be-used.spec.js index 165e1979..9164fb1d 100644 --- a/docker/test/font-tag-must-not-be-used.spec.js +++ b/docker/test/font-tag-must-not-be-used.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Anything that is not font tag should not result in an error', () => { const code = 'Text
' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/grammar-scrum-terms.spec.js b/docker/test/grammar-scrum-terms.spec.js index a2ccff16..3ade66f1 100644 --- a/docker/test/grammar-scrum-terms.spec.js +++ b/docker/test/grammar-scrum-terms.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruldId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it("Scrum terms that are cased correctly should not result in an error", () => { const code = "Scrum, Sprint, Product Owner, Scrum Master, Product Backlog, Sprint Review, Sprint Planning, Sprint Retrospective, Sprint Retro, Specification Review, Spec Review
"; diff --git a/docker/test/link-must-not-show-unc.spec.js b/docker/test/link-must-not-show-unc.spec.js index 5584a133..f48998ea 100644 --- a/docker/test/link-must-not-show-unc.spec.js +++ b/docker/test/link-must-not-show-unc.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Href that does not show UNC should not result in an error', () => { const code = 'somelink' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/meta-tags-must-not-redirect.spec.js b/docker/test/meta-tags-must-not-redirect.spec.js index f407a16a..ddecba3b 100644 --- a/docker/test/meta-tags-must-not-redirect.spec.js +++ b/docker/test/meta-tags-must-not-redirect.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Meta tags that does not refresh should not result in an error', () => { const code = '' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/page-must-not-show-email-addresses.spec.js b/docker/test/page-must-not-show-email-addresses.spec.js index e24babbe..7121a32f 100644 --- a/docker/test/page-must-not-show-email-addresses.spec.js +++ b/docker/test/page-must-not-show-email-addresses.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('Page that does not show email addresses should not result in an error', () => { const code = 'email address' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/phone-numbers-without-links.js b/docker/test/phone-numbers-without-links.js index d497254a..87e8406c 100644 --- a/docker/test/phone-numbers-without-links.js +++ b/docker/test/phone-numbers-without-links.js @@ -23,11 +23,15 @@ const nonPhoneNumbers = [ "2023.05.31.02", "123.456.7890", "1234.567.890", - "20231024.16" + "20231024.16", + "20231102.4" ]; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruleId}`, () => { - addCustomHtmlRule(); phoneNumbers.forEach((phone) => { it(`text containing "${phone}" without hyperlink should error`, () => { diff --git a/docker/test/url-must-be-formatted-correctly.spec.js b/docker/test/url-must-be-formatted-correctly.spec.js index eb030877..cc0d9aaa 100644 --- a/docker/test/url-must-be-formatted-correctly.spec.js +++ b/docker/test/url-must-be-formatted-correctly.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruldId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it("URL with full stop at the end should result in an error", () => { const code = ''; const messages = HTMLHint.verify(code, ruleOptions); diff --git a/docker/test/url-must-not-have-click-here.spec.js b/docker/test/url-must-not-have-click-here.spec.js index fe0ceef7..bf5ede55 100644 --- a/docker/test/url-must-not-have-click-here.spec.js +++ b/docker/test/url-must-not-have-click-here.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('URL text without words click here should not result in an error', () => { const code = 'Not Click Here' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/url-must-not-have-space.spec.js b/docker/test/url-must-not-have-space.spec.js index 0a85a2c0..81f909f6 100644 --- a/docker/test/url-must-not-have-space.spec.js +++ b/docker/test/url-must-not-have-space.spec.js @@ -9,8 +9,11 @@ const {addCustomHtmlRule} = require('../customHtmlRules') ruleOptions[ruldId] = true +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it('URL without space should not result in an error', () => { const code = '' const messages = HTMLHint.verify(code, ruleOptions) diff --git a/docker/test/use-unicode-hex-code-for-special-html-characters.spec.js b/docker/test/use-unicode-hex-code-for-special-html-characters.spec.js index d506f70e..27041d6c 100644 --- a/docker/test/use-unicode-hex-code-for-special-html-characters.spec.js +++ b/docker/test/use-unicode-hex-code-for-special-html-characters.spec.js @@ -9,8 +9,11 @@ const { addCustomHtmlRule } = require("../customHtmlRules"); ruleOptions[ruldId] = true; +before(async () => { + await addCustomHtmlRule(); +}); + describe(`Rules: ${ruldId}`, () => { - addCustomHtmlRule(); it("Non code tag with special char should result in an error", () => { const code = 'aaa>bbb