mirror of
https://github.com/Alvin-Zilverstand/return-youtube-dislike.git
synced 2026-09-12 09:58:20 +02:00
Feature/add unit testing framework and start writing unit tests (acceptance tests) (#708)
* add jest and rewire for testing * wrap loose code for initMutationObserver() in function and call it instead of just executing it Otherwise this code is executed everytime state.js is imported somewhere in a file. This leads to not being able to test properly, since state.js is imported in a lot of the src files and fails when running the tests since we are missing global environment settings such as document, window etc in the jest environment. * export roundDown and getNumberformatter to make them testable as well * add basic acceptance tests for utils.js, create snapshots and add jest config * add failing test for buggy behaviour in a separate test case and skip it until fixed * Fixes after merge conflicts --------- Co-authored-by: Dmitrii Selivanov <selivano.d@gmail.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env"],
|
"presets": ["@babel/preset-env"],
|
||||||
|
"plugins": ["rewire"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,17 +32,12 @@ api.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||||||
} else if (request.message == "set_state") {
|
} else if (request.message == "set_state") {
|
||||||
// chrome.identity.getAuthToken({ interactive: true }, function (token) {
|
// chrome.identity.getAuthToken({ interactive: true }, function (token) {
|
||||||
let token = "";
|
let token = "";
|
||||||
fetch(
|
fetch(`${apiUrl}/votes?videoId=${request.videoId}&likeCount=${request.likeCount || ""}`, {
|
||||||
`${apiUrl}/votes?videoId=${request.videoId}&likeCount=${
|
method: "GET",
|
||||||
request.likeCount || ""
|
headers: {
|
||||||
}`,
|
Accept: "application/json",
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
sendResponse(response);
|
sendResponse(response);
|
||||||
@@ -147,15 +142,12 @@ async function sendVote(videoId, vote) {
|
|||||||
async function register() {
|
async function register() {
|
||||||
const userId = generateUserID();
|
const userId = generateUserID();
|
||||||
api.storage.sync.set({ userId });
|
api.storage.sync.set({ userId });
|
||||||
const registrationResponse = await fetch(
|
const registrationResponse = await fetch(`${apiUrl}/puzzle/registration?userId=${userId}`, {
|
||||||
`${apiUrl}/puzzle/registration?userId=${userId}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
Accept: "application/json",
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
).then((response) => response.json());
|
}).then((response) => response.json());
|
||||||
const solvedPuzzle = await solvePuzzle(registrationResponse);
|
const solvedPuzzle = await solvePuzzle(registrationResponse);
|
||||||
if (!solvedPuzzle.solution) {
|
if (!solvedPuzzle.solution) {
|
||||||
await register();
|
await register();
|
||||||
@@ -210,9 +202,7 @@ function countLeadingZeroes(uInt8View, limit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function solvePuzzle(puzzle) {
|
async function solvePuzzle(puzzle) {
|
||||||
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) =>
|
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) => c.charCodeAt(0));
|
||||||
c.charCodeAt(0),
|
|
||||||
);
|
|
||||||
let buffer = new ArrayBuffer(20);
|
let buffer = new ArrayBuffer(20);
|
||||||
let uInt8View = new Uint8Array(buffer);
|
let uInt8View = new Uint8Array(buffer);
|
||||||
let uInt32View = new Uint32Array(buffer);
|
let uInt32View = new Uint32Array(buffer);
|
||||||
@@ -235,8 +225,7 @@ async function solvePuzzle(puzzle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateUserID(length = 36) {
|
function generateUserID(length = 36) {
|
||||||
const charset =
|
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
let result = "";
|
let result = "";
|
||||||
if (crypto && crypto.getRandomValues) {
|
if (crypto && crypto.getRandomValues) {
|
||||||
const values = new Uint32Array(length);
|
const values = new Uint32Array(length);
|
||||||
@@ -255,9 +244,7 @@ function generateUserID(length = 36) {
|
|||||||
|
|
||||||
function storageChangeHandler(changes, area) {
|
function storageChangeHandler(changes, area) {
|
||||||
if (changes.disableVoteSubmission !== undefined) {
|
if (changes.disableVoteSubmission !== undefined) {
|
||||||
handleDisableVoteSubmissionChangeEvent(
|
handleDisableVoteSubmissionChangeEvent(changes.disableVoteSubmission.newValue);
|
||||||
changes.disableVoteSubmission.newValue,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (changes.coloredThumbs !== undefined) {
|
if (changes.coloredThumbs !== undefined) {
|
||||||
handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);
|
handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);
|
||||||
@@ -272,21 +259,16 @@ function storageChangeHandler(changes, area) {
|
|||||||
handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);
|
handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);
|
||||||
}
|
}
|
||||||
if (changes.numberDisplayReformatLikes !== undefined) {
|
if (changes.numberDisplayReformatLikes !== undefined) {
|
||||||
handleNumberDisplayReformatLikesChangeEvent(
|
handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);
|
||||||
changes.numberDisplayReformatLikes.newValue,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (changes.disableLogging !== undefined) {
|
if (changes.disableLogging !== undefined) {
|
||||||
handleDisableLoggingChangeEvent(changes.disableLogging.newValue);
|
handleDisableLoggingChangeEvent(changes.disableLogging.newValue);
|
||||||
|
}
|
||||||
if (changes.showTooltipPercentage !== undefined) {
|
if (changes.showTooltipPercentage !== undefined) {
|
||||||
handleShowTooltipPercentageChangeEvent(
|
handleShowTooltipPercentageChangeEvent(changes.showTooltipPercentage.newValue);
|
||||||
changes.showTooltipPercentage.newValue,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (changes.numberDisplayReformatLikes !== undefined) {
|
if (changes.numberDisplayReformatLikes !== undefined) {
|
||||||
handleNumberDisplayReformatLikesChangeEvent(
|
handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);
|
||||||
changes.numberDisplayReformatLikes.newValue,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,10 +300,8 @@ function handleTooltipPercentageModeChangeEvent(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changeIcon(iconName) {
|
function changeIcon(iconName) {
|
||||||
if (api.action !== undefined)
|
if (api.action !== undefined) api.action.setIcon({ path: "/icons/" + iconName });
|
||||||
api.action.setIcon({ path: "/icons/" + iconName });
|
else if (api.browserAction !== undefined) api.browserAction.setIcon({ path: "/icons/" + iconName });
|
||||||
else if (api.browserAction !== undefined)
|
|
||||||
api.browserAction.setIcon({ path: "/icons/" + iconName });
|
|
||||||
else console.log("changing icon is not supported");
|
else console.log("changing icon is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,12 +349,11 @@ function initializeDisableVoteSubmission() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeDisableLogging(){
|
function initializeDisableLogging() {
|
||||||
api.storage.sync.get(['disableLogging'],(res)=>{
|
api.storage.sync.get(["disableLogging"], (res) => {
|
||||||
if (res.disableLogging === undefined) {
|
if (res.disableLogging === undefined) {
|
||||||
api.storage.sync.set({disableLogging:true});
|
api.storage.sync.set({ disableLogging: true });
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
extConfig.disableLogging = res.disableLogging;
|
extConfig.disableLogging = res.disableLogging;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -454,7 +433,5 @@ function isChrome() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isFirefox() {
|
function isFirefox() {
|
||||||
return (
|
return typeof browser !== "undefined" && typeof browser.runtime !== "undefined";
|
||||||
typeof browser !== "undefined" && typeof browser.runtime !== "undefined"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter when falling back to default locale 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter when using chrome 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter when using firefox 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter when using the URL locale (possibly buggy) 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter when using the URL locale 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter whith standard option 1`] = `NumberFormat {}`;
|
||||||
|
|
||||||
|
exports[`getNumberFormatter should return a correct formatter with compactLong option 1`] = `NumberFormat {}`;
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { getLikeButton, getDislikeButton, getButtons, getLikeTextContainer, getDislikeTextContainer } from "./buttons";
|
||||||
getLikeButton,
|
|
||||||
getDislikeButton,
|
|
||||||
getButtons,
|
|
||||||
getLikeTextContainer,
|
|
||||||
getDislikeTextContainer,
|
|
||||||
} from "./buttons";
|
|
||||||
import { createRateBar } from "./bar";
|
import { createRateBar } from "./bar";
|
||||||
import {
|
import {
|
||||||
getBrowser,
|
getBrowser,
|
||||||
@@ -25,7 +19,7 @@ const NEUTRAL_STATE = "NEUTRAL_STATE";
|
|||||||
|
|
||||||
let extConfig = {
|
let extConfig = {
|
||||||
disableVoteSubmission: false,
|
disableVoteSubmission: false,
|
||||||
disableLogging: true,
|
disableLogging: false,
|
||||||
coloredThumbs: false,
|
coloredThumbs: false,
|
||||||
coloredBar: false,
|
coloredBar: false,
|
||||||
colorTheme: "classic",
|
colorTheme: "classic",
|
||||||
@@ -110,11 +104,7 @@ if (isShorts() && !shortsObserver) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cLog(
|
cLog("Unexpected mutation observer event: " + mutation.target + mutation.type);
|
||||||
"Unexpected mutation observer event: " +
|
|
||||||
mutation.target +
|
|
||||||
mutation.type,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -123,38 +113,28 @@ if (isShorts() && !shortsObserver) {
|
|||||||
function isLikesDisabled() {
|
function isLikesDisabled() {
|
||||||
// return true if the like button's text doesn't contain any number
|
// return true if the like button's text doesn't contain any number
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
return /^\D*$/.test(
|
return /^\D*$/.test(getButtons().children[0].querySelector(".button-renderer-text").innerText);
|
||||||
getButtons().children[0].querySelector(".button-renderer-text").innerText,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return /^\D*$/.test(getLikeTextContainer().innerText);
|
return /^\D*$/.test(getLikeTextContainer().innerText);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVideoLiked() {
|
function isVideoLiked() {
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
return (
|
return getLikeButton().querySelector("button").getAttribute("aria-label") === "true";
|
||||||
getLikeButton().querySelector("button").getAttribute("aria-label") ===
|
|
||||||
"true"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
getLikeButton().classList.contains("style-default-active") ||
|
getLikeButton().classList.contains("style-default-active") ||
|
||||||
getLikeButton().querySelector("button")?.getAttribute("aria-pressed") ===
|
getLikeButton().querySelector("button")?.getAttribute("aria-pressed") === "true"
|
||||||
"true"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVideoDisliked() {
|
function isVideoDisliked() {
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
return (
|
return getDislikeButton().querySelector("button").getAttribute("aria-label") === "true";
|
||||||
getDislikeButton().querySelector("button").getAttribute("aria-label") ===
|
|
||||||
"true"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
getDislikeButton().classList.contains("style-default-active") ||
|
getDislikeButton().classList.contains("style-default-active") ||
|
||||||
getDislikeButton().querySelector("button")?.getAttribute("aria-pressed") ===
|
getDislikeButton().querySelector("button")?.getAttribute("aria-pressed") === "true"
|
||||||
"true"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,18 +159,14 @@ function setDislikes(dislikesCount) {
|
|||||||
getDislikeTextContainer()?.removeAttribute("is-empty");
|
getDislikeTextContainer()?.removeAttribute("is-empty");
|
||||||
if (!isLikesDisabled()) {
|
if (!isLikesDisabled()) {
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
getButtons().children[1].querySelector(
|
getButtons().children[1].querySelector(".button-renderer-text").innerText = dislikesCount;
|
||||||
".button-renderer-text",
|
|
||||||
).innerText = dislikesCount;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getDislikeTextContainer().innerText = dislikesCount;
|
getDislikeTextContainer().innerText = dislikesCount;
|
||||||
} else {
|
} else {
|
||||||
cLog("likes count disabled by creator");
|
cLog("likes count disabled by creator");
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
getButtons().children[1].querySelector(
|
getButtons().children[1].querySelector(".button-renderer-text").innerText = localize("TextLikesDisabled");
|
||||||
".button-renderer-text",
|
|
||||||
).innerText = localize("TextLikesDisabled");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getDislikeTextContainer().innerText = localize("TextLikesDisabled");
|
getDislikeTextContainer().innerText = localize("TextLikesDisabled");
|
||||||
@@ -206,8 +182,7 @@ function getLikeCountFromButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let likeButton =
|
let likeButton =
|
||||||
getLikeButton().querySelector("yt-formatted-string#text") ??
|
getLikeButton().querySelector("yt-formatted-string#text") ?? getLikeButton().querySelector("button");
|
||||||
getLikeButton().querySelector("button");
|
|
||||||
|
|
||||||
let likesStr = likeButton.getAttribute("aria-label").replace(/\D/g, "");
|
let likesStr = likeButton.getAttribute("aria-label").replace(/\D/g, "");
|
||||||
return likesStr.length > 0 ? parseInt(likesStr) : false;
|
return likesStr.length > 0 ? parseInt(likesStr) : false;
|
||||||
@@ -231,12 +206,8 @@ function processResponse(response, storedData) {
|
|||||||
if (extConfig.coloredThumbs === true) {
|
if (extConfig.coloredThumbs === true) {
|
||||||
if (isShorts()) {
|
if (isShorts()) {
|
||||||
// for shorts, leave deactivated buttons in default color
|
// for shorts, leave deactivated buttons in default color
|
||||||
let shortLikeButton = getLikeButton().querySelector(
|
let shortLikeButton = getLikeButton().querySelector("tp-yt-paper-button#button");
|
||||||
"tp-yt-paper-button#button",
|
let shortDislikeButton = getDislikeButton().querySelector("tp-yt-paper-button#button");
|
||||||
);
|
|
||||||
let shortDislikeButton = getDislikeButton().querySelector(
|
|
||||||
"tp-yt-paper-button#button",
|
|
||||||
);
|
|
||||||
if (shortLikeButton.getAttribute("aria-pressed") === "true") {
|
if (shortLikeButton.getAttribute("aria-pressed") === "true") {
|
||||||
shortLikeButton.style.color = getColorFromTheme(true);
|
shortLikeButton.style.color = getColorFromTheme(true);
|
||||||
}
|
}
|
||||||
@@ -260,26 +231,19 @@ function displayError(error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setState(storedData) {
|
async function setState(storedData) {
|
||||||
storedData.previousState = isVideoDisliked()
|
storedData.previousState = isVideoDisliked() ? DISLIKED_STATE : isVideoLiked() ? LIKED_STATE : NEUTRAL_STATE;
|
||||||
? DISLIKED_STATE
|
|
||||||
: isVideoLiked()
|
|
||||||
? LIKED_STATE
|
|
||||||
: NEUTRAL_STATE;
|
|
||||||
let statsSet = false;
|
let statsSet = false;
|
||||||
cLog("Video is loaded. Adding buttons...");
|
cLog("Video is loaded. Adding buttons...");
|
||||||
|
|
||||||
let videoId = getVideoId(window.location.href);
|
let videoId = getVideoId(window.location.href);
|
||||||
let likeCount = getLikeCountFromButton() || null;
|
let likeCount = getLikeCountFromButton() || null;
|
||||||
|
|
||||||
let response = await fetch(
|
let response = await fetch(`${apiUrl}/votes?videoId=${videoId}&likeCount=${likeCount || ""}`, {
|
||||||
`${apiUrl}/votes?videoId=${videoId}&likeCount=${likeCount || ""}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
Accept: "application/json",
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) displayError(response.error);
|
if (!response.ok) displayError(response.error);
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ function getVideoId(url) {
|
|||||||
const urlObject = new URL(url);
|
const urlObject = new URL(url);
|
||||||
const pathname = urlObject.pathname;
|
const pathname = urlObject.pathname;
|
||||||
if (pathname.startsWith("/clip")) {
|
if (pathname.startsWith("/clip")) {
|
||||||
return (document.querySelector("meta[itemprop='videoId']") || document.querySelector("meta[itemprop='identifier']")).content;
|
return (document.querySelector("meta[itemprop='videoId']") || document.querySelector("meta[itemprop='identifier']"))
|
||||||
|
.content;
|
||||||
} else {
|
} else {
|
||||||
if (pathname.startsWith("/shorts")) {
|
if (pathname.startsWith("/shorts")) {
|
||||||
return pathname.slice(8);
|
return pathname.slice(8);
|
||||||
@@ -103,7 +104,7 @@ function isVideoLoaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cLog(message, writer) {
|
function cLog(message, writer) {
|
||||||
if (!extConfig.disableLogging){
|
if (!extConfig.disableLogging) {
|
||||||
message = `[return youtube dislike]: ${message}`;
|
message = `[return youtube dislike]: ${message}`;
|
||||||
if (writer) {
|
if (writer) {
|
||||||
writer(message);
|
writer(message);
|
||||||
@@ -177,6 +178,7 @@ function createObserver(options, callback) {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
numberFormat,
|
numberFormat,
|
||||||
|
getNumberFormatter,
|
||||||
getBrowser,
|
getBrowser,
|
||||||
getVideoId,
|
getVideoId,
|
||||||
isInViewport,
|
isInViewport,
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
__Rewire__ as rewiredUtils,
|
||||||
|
numberFormat,
|
||||||
|
getNumberFormatter,
|
||||||
|
localize,
|
||||||
|
getBrowser,
|
||||||
|
getVideoId,
|
||||||
|
isInViewport,
|
||||||
|
isVideoLoaded,
|
||||||
|
cLog,
|
||||||
|
getColorFromTheme,
|
||||||
|
} from "./utils";
|
||||||
|
import { extConfig } from "./state";
|
||||||
|
|
||||||
|
jest.mock("./state");
|
||||||
|
|
||||||
|
describe("numberFormat", () => {
|
||||||
|
it("should format high numbers correctly", () => {
|
||||||
|
rewiredUtils(
|
||||||
|
"getNumberFormatter",
|
||||||
|
jest.fn().mockReturnValue(
|
||||||
|
Intl.NumberFormat("en", {
|
||||||
|
notation: "compact",
|
||||||
|
compactDisplay: "short",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(numberFormat(91492)).toBe("91K");
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not format input when < 1000", () => {
|
||||||
|
rewiredUtils(
|
||||||
|
"getNumberFormatter",
|
||||||
|
jest.fn().mockReturnValue(
|
||||||
|
Intl.NumberFormat("en", {
|
||||||
|
notation: "compact",
|
||||||
|
compactDisplay: "short",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(numberFormat(100)).toBe("100");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not round down when rounding is diabled in config", () => {
|
||||||
|
rewiredUtils(
|
||||||
|
"getNumberFormatter",
|
||||||
|
jest.fn().mockReturnValue(
|
||||||
|
Intl.NumberFormat("de", {
|
||||||
|
notation: "compact",
|
||||||
|
compactDisplay: "short",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
extConfig.numberDisplayRoundDown = false;
|
||||||
|
|
||||||
|
expect(numberFormat(912391)).toBe("912.391");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getNumberFormatter", () => {
|
||||||
|
it("should return a correct formatter with compactLong option", () => {
|
||||||
|
expect(getNumberFormatter("compactLong")).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a correct formatter whith standard option", () => {
|
||||||
|
expect(getNumberFormatter("standard")).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a correct formatter when using chrome", () => {
|
||||||
|
Object.defineProperty(document.documentElement, "lang", {
|
||||||
|
value: "en",
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
expect(getNumberFormatter()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a correct formatter when using firefox", () => {
|
||||||
|
Object.defineProperty(document.documentElement, "lang", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
expect(getNumberFormatter()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a correct formatter when using the URL locale (possibly buggy)", () => {
|
||||||
|
// Disclaimer: The case we are testing here is actually not correct and
|
||||||
|
// am almost sure I found a bug in the actual implementation of
|
||||||
|
// getNumberFormatter(). As I am currently writing acceptance tests only
|
||||||
|
// e.g. tests that are exclusively testing the current behaviour as is, as
|
||||||
|
// a first step of adding tests to the codebase, I will add it like this
|
||||||
|
// for now and document how we should actually be testing down below.
|
||||||
|
// The reason why this bug is not causing faulty behaviour is, that we
|
||||||
|
// have a fallback for it, that is always triggered.
|
||||||
|
|
||||||
|
// This is how we make the test go green:
|
||||||
|
Object.defineProperty(document.documentElement, "lang", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(navigator, "language", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
const mockedNode = document.createElement("link");
|
||||||
|
|
||||||
|
mockedNode.setAttribute("href", "https://www.youtube.com/opensearch?locale=en");
|
||||||
|
|
||||||
|
document.querySelectorAll = jest.fn().mockReturnValue([mockedNode]);
|
||||||
|
expect(getNumberFormatter()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip("should return a correct formatter when using the URL locale", () => {
|
||||||
|
// But we actually want to test like so, which is the correct value of the locale attribute:
|
||||||
|
Object.defineProperty(document.documentElement, "lang", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(navigator, "language", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
const mockedNode = document.createElement("link");
|
||||||
|
mockedNode.setAttribute("href", "https://www.youtube.com/opensearch?locale=en_US");
|
||||||
|
|
||||||
|
document.querySelectorAll = jest.fn().mockReturnValue([mockedNode]);
|
||||||
|
expect(getNumberFormatter()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a correct formatter when falling back to default locale", () => {
|
||||||
|
Object.defineProperty(document.documentElement, "lang", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(navigator, "language", {
|
||||||
|
value: null,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
expect(getNumberFormatter()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("localize", () => {
|
||||||
|
it("should return a translated string", () => {
|
||||||
|
global.chrome = {
|
||||||
|
i18n: {
|
||||||
|
getMessage: () => "Return YouTube Dislike",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(localize("extensionName")).toBe("Return YouTube Dislike");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getBrowser", () => {
|
||||||
|
it("should return a chrome browser instance", () => {
|
||||||
|
global.chrome = {
|
||||||
|
runtime: jest.fn(),
|
||||||
|
};
|
||||||
|
expect(getBrowser()).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a mozilla browser instance", () => {
|
||||||
|
global.chrome.runtime = undefined;
|
||||||
|
global.browser = {
|
||||||
|
runtime: jest.fn(),
|
||||||
|
};
|
||||||
|
expect(getBrowser()).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail on unsupported browser", () => {
|
||||||
|
global.chrome = undefined;
|
||||||
|
global.browser = undefined;
|
||||||
|
|
||||||
|
const log = console.log;
|
||||||
|
console.log = jest.fn();
|
||||||
|
|
||||||
|
expect(getBrowser()).toBe(false);
|
||||||
|
expect(console.log).toBeCalledWith("browser is not supported");
|
||||||
|
|
||||||
|
console.log = log;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("getVideoId", () => {
|
||||||
|
it("should return the video Id on valid url input", () => {
|
||||||
|
const url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
||||||
|
expect(getVideoId(url)).toBe("dQw4w9WgXcQ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the video Id on valid shorts url input", () => {
|
||||||
|
const url = "https://www.youtube.com/shorts/niMcQ9Vmmj8";
|
||||||
|
expect(getVideoId(url)).toBe("niMcQ9Vmmj8");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the video Id on clip url input", () => {
|
||||||
|
document.querySelector = jest.fn().mockReturnValue({
|
||||||
|
content: "niMcQ9Vmmj8",
|
||||||
|
});
|
||||||
|
const url = "https://www.youtube.com/clip";
|
||||||
|
expect(getVideoId(url)).toBe("niMcQ9Vmmj8");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null on invalid url input", () => {
|
||||||
|
const url = "https://www.youtube.com/wrong-url";
|
||||||
|
expect(getVideoId(url)).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("isInViewport", () => {
|
||||||
|
it("should return true if element is in viewport", () => {
|
||||||
|
const rect = {
|
||||||
|
getBoundingClientRect: jest.fn().mockReturnValue({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
expect(isInViewport(rect)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if element is out of viewport", () => {
|
||||||
|
const rect = {
|
||||||
|
getBoundingClientRect: jest.fn().mockReturnValue({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 769,
|
||||||
|
right: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
expect(isInViewport(rect)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("isVideoLoaded", () => {
|
||||||
|
describe("on desktop", () => {
|
||||||
|
it("should return true on loaded video", () => {
|
||||||
|
rewiredUtils("getVideoId", jest.fn().mockReturnValue("fakeId"));
|
||||||
|
document.querySelector = jest.fn().mockReturnValue("notNull");
|
||||||
|
expect(isVideoLoaded()).toBe(true);
|
||||||
|
expect(document.querySelector).toBeCalledWith("ytd-watch-grid[video-id='fakeId']");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false on not loaded video", () => {
|
||||||
|
rewiredUtils("getVideoId", jest.fn().mockReturnValue("fakeId"));
|
||||||
|
document.querySelector = jest.fn().mockReturnValue(null);
|
||||||
|
expect(isVideoLoaded()).toBe(false);
|
||||||
|
expect(document.querySelector).toBeCalledWith("ytd-watch-flexy[video-id='fakeId']");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("on mobile", () => {
|
||||||
|
it("should return true on loaded video", () => {
|
||||||
|
rewiredUtils("getVideoId", jest.fn().mockReturnValue("fakeId"));
|
||||||
|
document.querySelector = jest.fn().mockReturnValueOnce(null).mockReturnValue("notNull");
|
||||||
|
expect(isVideoLoaded()).toBe(true);
|
||||||
|
expect(document.querySelector).toHaveBeenLastCalledWith("ytd-watch-flexy[video-id='fakeId']");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false on not loaded video", () => {
|
||||||
|
rewiredUtils("getVideoId", jest.fn().mockReturnValue("fakeId"));
|
||||||
|
document.querySelector = jest.fn().mockReturnValueOnce(null).mockReturnValue(null);
|
||||||
|
expect(isVideoLoaded()).toBe(false);
|
||||||
|
expect(document.querySelector).toHaveBeenLastCalledWith('#player[loading="false"]:not([hidden])');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("cLog", () => {
|
||||||
|
it("should log a message with the correct prefix", () => {
|
||||||
|
const log = console.log;
|
||||||
|
console.log = jest.fn();
|
||||||
|
cLog("Test message");
|
||||||
|
|
||||||
|
expect(console.log).toBeCalledWith("[return youtube dislike]: Test message");
|
||||||
|
|
||||||
|
console.log = log;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log a message with the correct prefix when given a writer", () => {
|
||||||
|
const fakeWriter = jest.fn();
|
||||||
|
|
||||||
|
cLog("Test message", fakeWriter);
|
||||||
|
expect(fakeWriter).toBeCalledWith("[return youtube dislike]: Test message");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("getColorFromTheme", () => {
|
||||||
|
describe("accessible", () => {
|
||||||
|
it("should return the correct color for like", () => {
|
||||||
|
extConfig.colorTheme = "accessible";
|
||||||
|
expect(getColorFromTheme(true)).toBe("dodgerblue");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the correct color for not liked", () => {
|
||||||
|
extConfig.colorTheme = "accessible";
|
||||||
|
expect(getColorFromTheme(false)).toBe("gold");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("neon", () => {
|
||||||
|
it("should return the correct color for like", () => {
|
||||||
|
extConfig.colorTheme = "neon";
|
||||||
|
expect(getColorFromTheme(true)).toBe("aqua");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the correct color for not liked", () => {
|
||||||
|
extConfig.colorTheme = "neon";
|
||||||
|
expect(getColorFromTheme(false)).toBe("magenta");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("classic", () => {
|
||||||
|
it("should return the correct color for like", () => {
|
||||||
|
extConfig.colorTheme = "classic";
|
||||||
|
expect(getColorFromTheme(true)).toBe("lime");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the correct color for not liked", () => {
|
||||||
|
extConfig.colorTheme = "classic";
|
||||||
|
expect(getColorFromTheme(false)).toBe("red");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
resetMocks: true,
|
||||||
|
collectCoverage: false,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
"Extensions/combined/src/**/*.js",
|
||||||
|
"Extensions/combined/*.js",
|
||||||
|
],
|
||||||
|
};
|
||||||
Generated
-8029
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -7,8 +7,8 @@
|
|||||||
"start": "echo To build for development, please use \"npm run dev\". To build for production, please use \"npm run build\".",
|
"start": "echo To build for development, please use \"npm run dev\". To build for production, please use \"npm run build\".",
|
||||||
"dev": "webpack --mode=production --watch",
|
"dev": "webpack --mode=production --watch",
|
||||||
"build": "webpack --mode=production",
|
"build": "webpack --mode=production",
|
||||||
|
"test": "jest",
|
||||||
"build:safari": "webpack --mode=production && xcrun safari-web-extension-converter Extensions/combined/dist/safari --project-location Extensions/combined/dist --bundle-identifier com.returnyoutubedislike.safari-ext --force",
|
"build:safari": "webpack --mode=production && xcrun safari-web-extension-converter Extensions/combined/dist/safari --project-location Extensions/combined/dist --bundle-identifier com.returnyoutubedislike.safari-ext --force",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
@@ -34,9 +34,12 @@
|
|||||||
"@babel/preset-env": "^7.23.5",
|
"@babel/preset-env": "^7.23.5",
|
||||||
"@babel/runtime": "^7.23.5",
|
"@babel/runtime": "^7.23.5",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
|
"babel-plugin-rewire": "^1.2.0",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"filemanager-webpack-plugin": "^8.0.0",
|
"filemanager-webpack-plugin": "^8.0.0",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.0",
|
||||||
|
"jest": "^28.1.3",
|
||||||
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"lint-staged": "^15.2.0",
|
"lint-staged": "^15.2.0",
|
||||||
"prettier": "^3.2.2",
|
"prettier": "^3.2.2",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user