mirror of
https://github.com/Anarios/return-youtube-dislike.git
synced 2026-10-27 23:31:06 +01:00
Expose liked state on ratio bar (#1335)
Expose data-ryd-liked for custom CSS and keep it synchronized with native reaction state. Fixes #95.
This commit is contained in:
committed by
GitHub
parent
b770a0102c
commit
da73060b79
@@ -1,5 +1,5 @@
|
||||
import { getButtons, getDislikeButton, getLikeButton } from "./buttons";
|
||||
import { extConfig, isMobile, isLikesDisabled, isNewDesign, isRoundedDesign, isShorts } from "./state";
|
||||
import { extConfig, isMobile, isLikesDisabled, isNewDesign, isRoundedDesign, isShorts, isVideoLiked } from "./state";
|
||||
import { getColorFromTheme, getElementWidth, getVideoId, isInViewport, querySelector } from "./utils";
|
||||
|
||||
function closestConfigured(element, selectors) {
|
||||
@@ -147,6 +147,7 @@ function createRateBar(likes, dislikes) {
|
||||
<div class="ryd-tooltip-bar-container">
|
||||
<div
|
||||
id="ryd-bar-container"
|
||||
data-ryd-liked="${isVideoLiked()}"
|
||||
style="width: 100%; height: 2px;${colorDislikeStyle}"
|
||||
>
|
||||
<div
|
||||
@@ -175,6 +176,7 @@ function createRateBar(likes, dislikes) {
|
||||
} else {
|
||||
const currentParts = getRateBarParts(buttons);
|
||||
currentParts.wrapper.setAttribute("data-ryd-video-id", videoId);
|
||||
currentParts.rateBar.setAttribute("data-ryd-liked", String(isVideoLiked()));
|
||||
currentParts.wrapper.style.width = widthPx + "px";
|
||||
currentParts.fill.style.width = widthPercent + "%";
|
||||
const tooltipHost = buttons.querySelector("#ryd-dislike-tooltip");
|
||||
|
||||
@@ -29,6 +29,7 @@ jest.mock("./state", () => ({
|
||||
isNewDesign: jest.fn(() => true),
|
||||
isRoundedDesign: jest.fn(() => true),
|
||||
isShorts: jest.fn(() => false),
|
||||
isVideoLiked: jest.fn(() => false),
|
||||
}));
|
||||
|
||||
jest.mock("./utils", () => ({
|
||||
@@ -48,8 +49,8 @@ jest.mock("./utils", () => ({
|
||||
|
||||
const { getButtons, getDislikeButton, getLikeButton } = require("./buttons");
|
||||
const { createRateBar, hasUsableRateBar } = require("./bar");
|
||||
const { isLikesDisabled, isMobile, isNewDesign, isRoundedDesign, isShorts } = require("./state");
|
||||
const { getVideoId } = require("./utils");
|
||||
const { isLikesDisabled, isMobile, isNewDesign, isRoundedDesign, isShorts, isVideoLiked } = require("./state");
|
||||
const { getVideoId, isInViewport } = require("./utils");
|
||||
|
||||
describe("rate-bar stylesheet contrast", () => {
|
||||
test("uses the same high-contrast negative track recipe as the userscript", () => {
|
||||
@@ -95,6 +96,7 @@ describe("rate-bar visual ownership", () => {
|
||||
isNewDesign.mockReturnValue(true);
|
||||
isRoundedDesign.mockReturnValue(true);
|
||||
isShorts.mockReturnValue(false);
|
||||
isVideoLiked.mockReturnValue(false);
|
||||
getVideoId.mockReturnValue("BBBBBBBBBBB");
|
||||
const buttons = document.querySelector('[data-watch-buttons="BBBBBBBBBBB"]');
|
||||
getButtons.mockReturnValue(buttons);
|
||||
@@ -102,6 +104,32 @@ describe("rate-bar visual ownership", () => {
|
||||
getDislikeButton.mockReturnValue(buttons.querySelector("dislike-button-view-model"));
|
||||
});
|
||||
|
||||
test.each([false, true])("initializes the custom CSS hook from the native liked state: %s", (liked) => {
|
||||
isVideoLiked.mockReturnValue(liked);
|
||||
|
||||
createRateBar(80, 20);
|
||||
|
||||
expect(getButtons().querySelector("#ryd-bar-container").getAttribute("data-ryd-liked")).toBe(String(liked));
|
||||
});
|
||||
|
||||
test("refreshes the liked hook when reusing or recreating the bar", () => {
|
||||
isInViewport.mockReturnValue(true);
|
||||
createRateBar(80, 20);
|
||||
const originalBar = getButtons().querySelector("#ryd-bar-container");
|
||||
isVideoLiked.mockReturnValue(true);
|
||||
|
||||
createRateBar(81, 20);
|
||||
|
||||
expect(getButtons().querySelector("#ryd-bar-container")).toBe(originalBar);
|
||||
expect(originalBar.getAttribute("data-ryd-liked")).toBe("true");
|
||||
|
||||
originalBar.closest(".ryd-tooltip").remove();
|
||||
createRateBar(81, 20);
|
||||
const recreatedBar = getButtons().querySelector("#ryd-bar-container");
|
||||
expect(recreatedBar).not.toBe(originalBar);
|
||||
expect(recreatedBar.getAttribute("data-ryd-liked")).toBe("true");
|
||||
});
|
||||
|
||||
test("mutates only the selected current watch tree when stale duplicate IDs come first", () => {
|
||||
const currentButtons = document.querySelector('[data-watch-buttons="BBBBBBBBBBB"]');
|
||||
expect(currentButtons.closest("#top-row")).toBe(document.querySelector('[data-watch-row="BBBBBBBBBBB"]'));
|
||||
@@ -160,6 +188,7 @@ describe("rate-bar visual ownership", () => {
|
||||
expect(currentButtons.querySelector("#ryd-bar").style.width).toBe("85.99%");
|
||||
|
||||
getVideoId.mockReturnValue("AAAAAAAAAAA");
|
||||
isVideoLiked.mockReturnValue(true);
|
||||
expect(hasUsableRateBar(currentButtons)).toBe(false);
|
||||
createRateBar(3_591, 1_409);
|
||||
|
||||
@@ -168,6 +197,7 @@ describe("rate-bar visual ownership", () => {
|
||||
expect(staleWrapper.isConnected).toBe(false);
|
||||
expect(currentWrapper).not.toBe(staleWrapper);
|
||||
expect(currentWrapper.getAttribute("data-ryd-video-id")).toBe("AAAAAAAAAAA");
|
||||
expect(currentWrapper.querySelector("#ryd-bar-container").getAttribute("data-ryd-liked")).toBe("true");
|
||||
expect(currentButtons.querySelector("#ryd-bar").style.width).toBe("71.82%");
|
||||
expect(hasUsableRateBar(currentButtons)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ function observeReactionPresentation(likeButton, dislikeButton) {
|
||||
presentationObserver.disconnect();
|
||||
const options = {
|
||||
attributes: true,
|
||||
attributeFilter: ["aria-label", "aria-pressed"],
|
||||
attributeFilter: ["aria-label", "aria-pressed", "class"],
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
NEUTRAL_STATE,
|
||||
} from "./state";
|
||||
|
||||
const { createRateBar: renderRateBar } = jest.requireActual("./bar");
|
||||
const innerTextDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerText");
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -148,6 +149,89 @@ test("native count markup is untouched when reformatting is off", async () => {
|
||||
expect(node.data).toBe("731");
|
||||
});
|
||||
|
||||
test("an initially liked video exposes its native state as soon as the bar is created", () => {
|
||||
const controls = mount();
|
||||
controls.nativeLikeButton.setAttribute("aria-pressed", "true");
|
||||
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
|
||||
expect(document.querySelector("#ryd-bar-container").getAttribute("data-ryd-liked")).toBe("true");
|
||||
});
|
||||
|
||||
test("native like, unlike, and dislike transitions update the CSS hook without changing bar styling", async () => {
|
||||
const controls = mount();
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
const bar = document.querySelector("#ryd-bar-container");
|
||||
const barStyle = bar.getAttribute("style");
|
||||
const fillStyle = bar.querySelector("#ryd-bar").getAttribute("style");
|
||||
expect(bar.getAttribute("data-ryd-liked")).toBe("false");
|
||||
refreshReactionPresentation();
|
||||
createRateBar.mockClear();
|
||||
|
||||
for (const [liked, disliked] of [
|
||||
[true, false],
|
||||
[false, false],
|
||||
[true, false],
|
||||
[false, true],
|
||||
]) {
|
||||
controls.nativeLikeButton.setAttribute("aria-pressed", String(liked));
|
||||
controls.nativeDislikeButton.setAttribute("aria-pressed", String(disliked));
|
||||
await flushMutations();
|
||||
|
||||
expect(bar.getAttribute("data-ryd-liked")).toBe(String(liked));
|
||||
expect(bar.getAttribute("style")).toBe(barStyle);
|
||||
expect(bar.querySelector("#ryd-bar").getAttribute("style")).toBe(fillStyle);
|
||||
}
|
||||
expect(storedData.likes).toBe(730);
|
||||
expect(createRateBar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("a replaced native like button refreshes the bar's liked hook", async () => {
|
||||
const controls = mount();
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
const replacement = controls.nativeLikeButton.cloneNode(true);
|
||||
replacement.setAttribute("aria-pressed", "true");
|
||||
|
||||
controls.nativeLikeButton.replaceWith(replacement);
|
||||
await flushMutations();
|
||||
|
||||
expect(document.querySelector("#ryd-bar-container").getAttribute("data-ryd-liked")).toBe("true");
|
||||
});
|
||||
|
||||
test("native active-class transitions refresh the bar's liked hook", async () => {
|
||||
const controls = mount();
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
const bar = document.querySelector("#ryd-bar-container");
|
||||
|
||||
controls.likeButton.classList.add("style-default-active");
|
||||
await flushMutations();
|
||||
expect(bar.getAttribute("data-ryd-liked")).toBe("true");
|
||||
|
||||
controls.likeButton.classList.remove("style-default-active");
|
||||
await flushMutations();
|
||||
expect(bar.getAttribute("data-ryd-liked")).toBe("false");
|
||||
});
|
||||
|
||||
test("navigation on reused controls replaces the outgoing bar's liked state", async () => {
|
||||
const controls = mount();
|
||||
controls.nativeLikeButton.setAttribute("aria-pressed", "true");
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
const outgoingBar = document.querySelector("#ryd-bar-container");
|
||||
expect(outgoingBar.getAttribute("data-ryd-liked")).toBe("true");
|
||||
|
||||
history.replaceState({}, "", "/watch?v=zyxwvutsrqp");
|
||||
Object.assign(storedData, { videoId: "zyxwvutsrqp", likes: 3591, dislikes: 1409 });
|
||||
controls.nativeLikeButton.setAttribute("aria-pressed", "false");
|
||||
renderRateBar(storedData.likes, storedData.dislikes);
|
||||
const destinationBar = document.querySelector("#ryd-bar-container");
|
||||
expect(destinationBar).not.toBe(outgoingBar);
|
||||
expect(destinationBar.getAttribute("data-ryd-liked")).toBe("false");
|
||||
|
||||
controls.nativeLikeButton.setAttribute("aria-pressed", "true");
|
||||
await flushMutations();
|
||||
expect(destinationBar.getAttribute("data-ryd-liked")).toBe("true");
|
||||
});
|
||||
|
||||
test("later native labels refresh stored likes and the rate bar when reformatting is off", async () => {
|
||||
const controls = mount();
|
||||
refreshReactionPresentation();
|
||||
|
||||
@@ -281,6 +281,11 @@ function setLikes(likesCount) {
|
||||
}
|
||||
|
||||
function refreshReactionPresentation() {
|
||||
const rateBar = getButtons()?.querySelector("#ryd-bar-container");
|
||||
if (rateBar) {
|
||||
const liked = String(isVideoLiked());
|
||||
if (rateBar.getAttribute("data-ryd-liked") !== liked) rateBar.setAttribute("data-ryd-liked", liked);
|
||||
}
|
||||
const nativeLikeCount = readNativeLikeCount();
|
||||
const nativeLikes = nativeLikeCount?.likes ?? false;
|
||||
if (nativeLikes > 0 && nativeLikeCount.videoId === storedData.videoId && !nativeLikeCount.presented) {
|
||||
|
||||
Reference in New Issue
Block a user