Moved to Template
All checks were successful
QS / QS (push) Successful in 7s

This commit is contained in:
Snoweuph 2024-12-21 16:09:09 +01:00
parent 1209b0ad5d
commit 83359af04a
Signed by: Snoweuph
GPG key ID: BEFC41DA223CEC55
15 changed files with 7597 additions and 12 deletions

View file

@ -0,0 +1 @@
test/data/**/*.html

View file

@ -8,7 +8,14 @@
[![Install Userscript](https://img.shields.io/badge/Install_Userscript-CCNA_Autofill-gray?style=for-the-badge&logo=greasyfork&logoColor=black&labelColor=brown)](https://git.euph.dev/Userscripts/CCNA_Autofill/raw/branch/main/src/main.user.js)
## Description
<!-- description here -->
A UserScript for autofilling the Answers on the **CCNA**.
inspired by [Merlinfuchs/ccna-extension](https://github.com/merlinfuchs/ccna-extension).
## Usage
<!-- usage explained here-->
This only works directly on the exam website.
- `p`: Paste your answer-URL from [ItexamAnswers](https://itexamanswers.net) for the **CCNAv7** use [these](https://itexamanswers.net/ccna-1-v7-modules-1-3-basic-network-connectivity-and-communications-exam-answers.html).
- `a`: Try autofilling the current answer. There won't be any visual feedback when it fails.
- `n`: Go to the next Question.

77
src/answer.js Normal file
View file

@ -0,0 +1,77 @@
/**
*
* @param {Array<Answer>} answerData
* @returns
*/
function answerQuestion(answerData) {
const question = document.querySelector('.question:not(.hidden)');
if (!question) {
return;
}
const questionTextDom = question.querySelector('.questionText .mattext');
if (!questionTextDom) return;
const questionText = questionTextDom.textContent.trim();
const answersDom = question.querySelector('ul.coreContent');
if (!answersDom) return;
const answers = answersDom.children;
for (let answer of Array.from(answers)) {
const input = answer.querySelector('input');
if (!input) continue;
input.checked = false;
}
const correctAnswers = findAnswers(answerData, questionText, answers);
if (correctAnswers.length === 0) {
return;
}
for (const answer of correctAnswers) {
const input = answer.querySelector('input');
if (!input) continue;
input.checked = true;
}
}
/**
*
* @param {Array<Answer>} answerData
* @param {string} questionText
* @returns
*/
function findAnswers(answerData, questionText, answers) {
if (answerData === null) {
return [];
}
const correctAnswers = [];
for (let entry of answerData) {
if (matchAnswer(questionText.trim(), entry.question.trim())) {
for (let availableAnswer of answers) {
for (let possibleAnswer of entry.answers) {
if (
matchAnswer(
availableAnswer.textContent.trim(),
possibleAnswer
)
) {
correctAnswers.push(availableAnswer);
}
}
}
}
}
return correctAnswers;
}
function matchAnswer(textA, textB) {
const replaceRegex = /[^\w]/gi;
textA = textA.replace(replaceRegex, '');
textB = textB.replace(replaceRegex, '');
return textA === textB;
}
window.answerQuestion = answerQuestion;

149
src/fetch.js Normal file
View file

@ -0,0 +1,149 @@
const QUESTION_REGEX = /^[0-9]+\. (.*)$/;
/**
* Fetches answers from the specified URL.
* @param {string} [answerURL=""] - The URL to fetch answers from.
* @returns {Promise<Array<Answer>>} A Promise that resolves with the fetched answers.
*/
function fetchAnswers(answerURL = '') {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: answerURL,
headers: {
'Content-Type': 'text/html'
},
onload: function (response) {
parseAnswers(response, resolve);
},
onerror: function (error) {
reject(error);
}
});
});
}
/**
*
* @param {GMXMLHttpRequestResponse} response
* @param {(value: Answer[] | PromiseLike<Answer[]>) => void} resolve
*/
function parseAnswers(response, resolve) {
const results = [];
const allAnswersElement = getAllAnswersElement(response);
let index = -1;
for (let child of Array.from(allAnswersElement.children)) {
index++;
const result = parseAnswerElement(index, child, allAnswersElement);
if (result != undefined) {
results.push(result);
}
}
resolve(results);
}
/**
* @param {GMXMLHttpRequestResponse} response
* @returns {Element}
*/
function getAllAnswersElement(response) {
const parser = new DOMParser();
const virtualDOM = parser.parseFromString(
response.responseText,
'text/html'
);
let answersElement = virtualDOM.querySelector('.pf-content');
if (!answersElement) {
answersElement = virtualDOM.querySelector('.thecontent');
}
return answersElement;
}
/**
* @param {number} index
* @param {Element} allAnswersElement
* @param {Element} element
* @returns {Answer}
*/
function parseAnswerElement(index, element, allAnswersElement) {
// Check for Possible Tags
if (
!(element.tagName === 'P' || element.tagName === 'STRONG') ||
!element.innerHTML
) {
return;
}
// Get Question Element
/** @type {Element} */
let questionElement = element.querySelector('strong');
if (questionElement === null) {
if (!element.textContent) {
return;
}
questionElement = element;
}
// Get Question
const questionText = parseQuestion(questionElement);
if (questionText === null) {
return;
}
// Get Awsners
const answersElement = getAnswersElement(index, allAnswersElement);
if (answersElement === null || answersElement.tagName !== 'UL') return;
return {
question: questionText,
answers: getAnswers(answersElement)
};
}
/**
* @param {Element} questionElement
* @returns {String}
*/
function parseQuestion(questionElement) {
const textContent = questionElement.textContent.trim();
const matches = textContent.match(QUESTION_REGEX);
return matches !== null ? matches[1] : null;
}
/**
* @param {number} index
* @param {Element} allAnswersElement
* @returns {Element}
*/
function getAnswersElement(index, allAnswersElement) {
let answersElement = allAnswersElement.children[index + 1];
if (answersElement.tagName === 'P') {
answersElement = allAnswersElement.children[index + 2];
}
return answersElement;
}
/**
* @param {Element} answersElement
* @returns {Array<string>}
*/
function getAnswers(answersElement) {
const answers = [];
for (let answerDom of Array.from(
answersElement.querySelectorAll('strong')
)) {
let answerText = answerDom.textContent.trim();
if (answerText.endsWith('*')) {
answerText = answerText.substring(0, answerText.length - 1);
}
answers.push(answerText);
}
return answers;
}
window.fetchAnswers = fetchAnswers;

View file

@ -1,5 +0,0 @@
function testLib() {
console.log('lib works!');
}
window.testLib = testLib;

View file

@ -1,13 +1,39 @@
// ==UserScript==
// @name CCNA_Autofill
// @namespace https://git.euph.dev/Userscripts
// @match *
// @require https://git.euph.dev/Userscripts/CCNA_Autofill/raw/branch/main/src/lib.js
// @match *://assessment.netacad.net/*
// @match *://www.assessment.netacad.net/*
// @require https://git.euph.dev/Userscripts/CCNA_Autofill/raw/branch/main/src/fetch.js
// @require https://git.euph.dev/Userscripts/CCNA_Autofill/raw/branch/main/src/answer.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @version 0.0.1
// @version 1.0.0
// @author Userscripts
// ==/UserScript==
window.testLib();
const URL_STORAGE_KEY = 'itexamanswers.net URL';
/** @type {Array<Answer>} */
let answerData;
window.addEventListener('keydown', async event => {
switch (event.key) {
case 'p':
const oldAnswersURL = GM_getValue(URL_STORAGE_KEY);
const newAnswersURL = prompt(
'Please input the answer url (itexamanswers.net)',
oldAnswersURL
);
GM_setValue(URL_STORAGE_KEY, newAnswersURL);
answerData = await window.fetchAnswers(newAnswersURL);
break;
case 'n':
document.getElementById('next').click();
break;
case 'a':
window.answerQuestion(answerData);
break;
}
});

1
test/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,60 @@
{
"name": "CCNA 1 v7 Modules 1 - 3 Basic Network Connectivity and Communications Exam Answers",
"file": "data.html",
"expected": [
{
"question": "What is an ISP?",
"answers": [
"It is an organization that enables individuals and businesses to connect to the Internet."
]
},
{
"question": "What type of network traffic requires QoS?",
"answers": ["video conferencing"]
},
{
"question": "Which interface allows remote management of a Layer 2 switch?",
"answers": ["the switch virtual interface"]
},
{
"question": "How is SSH different from Telnet?",
"answers": [
"SSH provides security to \nremote sessions by encrypting messages and using user authentication. \nTelnet is considered insecure and sends messages in plaintext."
]
},
{
"question": "What are three characteristics of an SVI? (Choose three.)",
"answers": [
"It is not associated with any physical interface on a switch.",
"It provides a means to remotely manage a switch.",
"It is associated with VLAN1 by default."
]
},
{
"question": "Which name is assigned to the transport layer PDU?",
"answers": ["segment"]
},
{
"question": "What process involves placing one PDU inside of another PDU?",
"answers": ["encapsulation"]
},
{
"question": "What is a characteristic of multicast messages?",
"answers": ["They are sent to a select group of hosts."]
},
{
"question": "Which statement is correct about network protocols?",
"answers": [
"They define how messages are exchanged between the source and the destination."
]
},
{
"question": "Why would a Layer 2 switch need an IP address?",
"answers": ["to enable the switch to be managed remotely"]
},
{
"question": "Which two devices are intermediary devices? (Choose two)",
"answers": ["Router", "Switch"]
}
]
}

32
test/fetch.test.js Normal file
View file

@ -0,0 +1,32 @@
const fs = require('fs');
const vm = require('vm');
const { JSDOM } = require('jsdom');
const { diff } = require('deep-diff');
const scriptContent = fs.readFileSync('../src/fetch.js', 'utf8');
/**
* @param testData {string}
* @param expectedData {Array<Answer>}
* @returns {Promise<Array<Answer>>}
*/
module.exports = async function executeFetchTest(testData, expectedData) {
const sandbox = {
window: {},
DOMParser: new JSDOM().window.DOMParser,
console: console
};
vm.createContext(sandbox);
vm.runInContext(scriptContent, sandbox);
const { parseAnswers } = sandbox;
return new Promise((resolve, reject) => {
parseAnswers({ responseText: testData }, results => {
const { diff } = require('deep-diff');
const changes = diff({ data: results }, { data: expectedData });
if (changes !== undefined) {
reject(changes);
}
resolve();
});
});
};

7
test/jsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"checkJs": true,
"module": "commonjs",
"target": "es6"
}
}

548
test/package-lock.json generated Normal file
View file

@ -0,0 +1,548 @@
{
"name": "test",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"chalk": "^4",
"deep-diff": "^1.0.2",
"jsdom": "^24.0.0",
"vm": "^0.1.0"
}
},
"node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/cssstyle": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
"integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
"license": "MIT",
"dependencies": {
"rrweb-cssom": "^0.7.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decimal.js": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"license": "MIT"
},
"node_modules/deep-diff": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
"integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^3.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"license": "MIT"
},
"node_modules/jsdom": {
"version": "24.1.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz",
"integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==",
"license": "MIT",
"dependencies": {
"cssstyle": "^4.0.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.12",
"parse5": "^7.1.2",
"rrweb-cssom": "^0.7.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^4.1.4",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0",
"ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"canvas": "^2.11.2"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nwsapi": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz",
"integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==",
"license": "MIT"
},
"node_modules/parse5": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
"license": "MIT",
"dependencies": {
"entities": "^4.5.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"license": "MIT"
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/rrweb-cssom": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
"integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/vm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/vm/-/vm-0.1.0.tgz",
"integrity": "sha512-1aKVjgohVDnVhGrJLhl2k8zIrapH+7HsdnIjGvBp3XX2OCj6XGzsIbDp9rZ3r7t6qgDfXEE1EoEAEOLJm9LKnw==",
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
"integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
"license": "MIT",
"dependencies": {
"tr46": "^5.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
}
}
}

View file

@ -1,5 +1,11 @@
{
"scripts": {
"test": "node test.js"
},
"dependencies": {
"chalk": "^4",
"deep-diff": "^1.0.2",
"jsdom": "^24.0.0",
"vm": "^0.1.0"
}
}

View file

@ -1 +1,70 @@
console.log('Test Okay');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const executeFetchTest = require('./fetch.test');
const ERROR_MESSAGE = chalk.red('[Error]');
const TEST_MESSAGE = chalk.blue('[Test]');
const SUCCESS_MESSAGE = chalk.green('Success:');
const FAILED_MESSAGE = chalk.red('Failed:');
(async () => {
let fail = 0;
const dataDirectory = path.join(__dirname, 'data');
const directories = fs.readdirSync(dataDirectory);
const promises = directories.map(directory => {
const testDataDirectory = path.join(dataDirectory, directory);
if (!fs.lstatSync(testDataDirectory).isDirectory()) {
console.error(
ERROR_MESSAGE,
'Test data directory is not a directory:',
testDataDirectory
);
return Promise.resolve();
}
const testJSONFilePath = path.join(testDataDirectory, 'test.json');
if (!fs.existsSync(testJSONFilePath)) {
console.error(
ERROR_MESSAGE,
'Test config file missing:',
testJSONFilePath
);
return Promise.resolve();
}
const data = fs.readFileSync(testJSONFilePath, 'utf8');
try {
const jsonData = JSON.parse(data);
const testData = fs.readFileSync(
path.join(testDataDirectory, jsonData.file),
'utf8'
);
return executeFetchTest(testData, jsonData.expected)
.then(() => {
console.log(TEST_MESSAGE, SUCCESS_MESSAGE, jsonData.name);
})
.catch(error => {
fail++;
console.error(
TEST_MESSAGE,
FAILED_MESSAGE,
jsonData.name,
error
);
});
} catch (parseErr) {
console.error(ERROR_MESSAGE, 'Parsing test config file:', parseErr);
return Promise.resolve();
}
});
await Promise.all(promises);
if (fail > 0) {
process.exitCode = 1;
console.error(`${ERROR_MESSAGE} Failed Tests: ${fail}`);
}
})();

5
types/Answer.d.js Normal file
View file

@ -0,0 +1,5 @@
/**
* @typedef {Object} Answer
* @property {string} question
* @property {Array<string>} answers
*/