mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
refactor(ai): extract notification logic from js/app.js (agent)
- Moved `showError`, `showStatus`, `showSuccess`, and `updateNotificationPortalVisibility` logic to `js/ui/notificationController.js`. - Instantiated `NotificationController` in `js/ui/applicationBootstrap.js`. - Refactored `js/app.js` to delegate notification calls to the controller. - Added unit tests for `NotificationController`. - Followed AGENTS.md refactoring checklist. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
cd6e5862d7
commit
1dfc23c11a
4 files changed
+424
-201
No files matched your search
@@ -9860,198 +9860,29 @@ class Application {
|
||||
}
|
||||
|
||||
updateNotificationPortalVisibility() {
|
||||
const portal = this.notificationPortal;
|
||||
const HTMLElementCtor =
|
||||
portal?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!portal || !HTMLElementCtor || !(portal instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
if (this.notificationController) {
|
||||
this.notificationController.updateNotificationPortalVisibility();
|
||||
}
|
||||
|
||||
const containers = [
|
||||
this.errorContainer,
|
||||
this.statusContainer,
|
||||
this.successContainer,
|
||||
];
|
||||
|
||||
const hasVisibleBanner = containers.some((container) => {
|
||||
if (!container || !(container instanceof HTMLElementCtor)) {
|
||||
return false;
|
||||
}
|
||||
return !container.classList.contains("hidden");
|
||||
});
|
||||
|
||||
portal.classList.toggle("notification-portal--active", hasVisibleBanner);
|
||||
}
|
||||
|
||||
showError(msg) {
|
||||
const container = this.errorContainer;
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
if (msg) {
|
||||
userLogger.error(msg);
|
||||
}
|
||||
return;
|
||||
if (this.notificationController) {
|
||||
this.notificationController.showError(msg);
|
||||
} else if (msg) {
|
||||
userLogger.error(msg);
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
// Remove any content, then hide
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a message, show it
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
userLogger.error(msg);
|
||||
|
||||
// Optional auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (container !== this.errorContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
showStatus(msg, options = {}) {
|
||||
const container = this.statusContainer;
|
||||
const messageTarget = this.statusMessage;
|
||||
const ownerDocument =
|
||||
container?.ownerDocument || (typeof document !== "undefined" ? document : null);
|
||||
const defaultView =
|
||||
ownerDocument?.defaultView || (typeof window !== "undefined" ? window : null);
|
||||
const clearScheduler =
|
||||
defaultView?.clearTimeout ||
|
||||
(typeof clearTimeout === "function" ? clearTimeout : null);
|
||||
const schedule =
|
||||
defaultView?.setTimeout || (typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (this.statusAutoHideHandle && typeof clearScheduler === "function") {
|
||||
clearScheduler(this.statusAutoHideHandle);
|
||||
this.statusAutoHideHandle = null;
|
||||
}
|
||||
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { autoHideMs, showSpinner } =
|
||||
options && typeof options === "object" ? options : Object.create(null);
|
||||
|
||||
const shouldShowSpinner = showSpinner !== false;
|
||||
const existingSpinner = container.querySelector(".status-spinner");
|
||||
|
||||
if (shouldShowSpinner) {
|
||||
if (!(existingSpinner instanceof HTMLElementCtor)) {
|
||||
const spinner = ownerDocument?.createElement?.("span") || null;
|
||||
if (spinner) {
|
||||
spinner.className = "status-spinner";
|
||||
spinner.setAttribute("aria-hidden", "true");
|
||||
if (messageTarget && container.contains(messageTarget)) {
|
||||
container.insertBefore(spinner, messageTarget);
|
||||
} else {
|
||||
container.insertBefore(spinner, container.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (existingSpinner instanceof HTMLElementCtor) {
|
||||
existingSpinner.remove();
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = "";
|
||||
}
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = msg;
|
||||
} else {
|
||||
container.textContent = msg;
|
||||
}
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
if (
|
||||
Number.isFinite(autoHideMs) &&
|
||||
autoHideMs > 0 &&
|
||||
typeof schedule === "function"
|
||||
) {
|
||||
const expectedText =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget.textContent
|
||||
: container.textContent;
|
||||
this.statusAutoHideHandle = schedule(() => {
|
||||
this.statusAutoHideHandle = null;
|
||||
const activeContainer = this.statusContainer;
|
||||
if (activeContainer !== container) {
|
||||
return;
|
||||
}
|
||||
const activeMessageTarget =
|
||||
this.statusMessage && typeof this.statusMessage.textContent === "string"
|
||||
? this.statusMessage
|
||||
: activeContainer;
|
||||
if (activeMessageTarget.textContent !== expectedText) {
|
||||
return;
|
||||
}
|
||||
if (activeMessageTarget === this.statusMessage) {
|
||||
this.statusMessage.textContent = "";
|
||||
} else {
|
||||
activeContainer.textContent = "";
|
||||
}
|
||||
activeContainer.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, autoHideMs);
|
||||
if (this.notificationController) {
|
||||
this.notificationController.showStatus(msg, options);
|
||||
}
|
||||
}
|
||||
|
||||
showSuccess(msg) {
|
||||
const container = this.successContainer;
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
if (this.notificationController) {
|
||||
this.notificationController.showSuccess(msg);
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
setTimeout(() => {
|
||||
if (container !== this.successContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
log(message, ...args) {
|
||||
@@ -10084,20 +9915,9 @@ class Application {
|
||||
this.moderationActionController = null;
|
||||
}
|
||||
|
||||
if (this.statusAutoHideHandle) {
|
||||
const ownerDocument =
|
||||
this.statusContainer?.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
const defaultView =
|
||||
ownerDocument?.defaultView ||
|
||||
(typeof window !== "undefined" ? window : null);
|
||||
const clearScheduler =
|
||||
defaultView?.clearTimeout ||
|
||||
(typeof clearTimeout === "function" ? clearTimeout : null);
|
||||
if (typeof clearScheduler === "function") {
|
||||
clearScheduler(this.statusAutoHideHandle);
|
||||
}
|
||||
this.statusAutoHideHandle = null;
|
||||
if (this.notificationController) {
|
||||
this.notificationController.destroy();
|
||||
this.notificationController = null;
|
||||
}
|
||||
|
||||
if (this.watchHistoryTelemetry) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { devLogger } from "../utils/logger.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { MediaLoader } from "../utils/mediaLoader.js";
|
||||
import { attachHealthBadges } from "../gridHealth.js";
|
||||
import { attachUrlHealthBadges } from "../urlHealthObserver.js";
|
||||
@@ -36,6 +36,7 @@ import { VideoListView } from "./views/VideoListView.js";
|
||||
import MoreMenuController from "./moreMenuController.js";
|
||||
import VideoSettingsMenuController from "./videoSettingsMenuController.js";
|
||||
import AppChromeController from "./appChromeController.js";
|
||||
import NotificationController from "./notificationController.js";
|
||||
import { getSidebarLoadingMarkup } from "../sidebarLoading.js";
|
||||
import { isWatchHistoryDebugEnabled } from "../watchHistoryDebug.js";
|
||||
import { splitAndZap as splitAndZapDefault } from "../payments/zapSplit.js";
|
||||
@@ -706,13 +707,19 @@ export default class ApplicationBootstrap {
|
||||
app.forYouLink = null;
|
||||
app.exploreLink = null;
|
||||
|
||||
app.notificationPortal = doc?.getElementById("notificationPortal") || null;
|
||||
app.errorContainer = doc?.getElementById("errorContainer") || null;
|
||||
app.successContainer = doc?.getElementById("successContainer") || null;
|
||||
app.statusContainer = doc?.getElementById("statusContainer") || null;
|
||||
app.statusMessage =
|
||||
app.statusContainer?.querySelector("[data-status-message]") || null;
|
||||
app.statusAutoHideHandle = null;
|
||||
app.notificationController = new NotificationController({
|
||||
portal: doc?.getElementById("notificationPortal") || null,
|
||||
errorContainer: doc?.getElementById("errorContainer") || null,
|
||||
successContainer: doc?.getElementById("successContainer") || null,
|
||||
statusContainer: doc?.getElementById("statusContainer") || null,
|
||||
loggers: {
|
||||
userLogger,
|
||||
devLogger,
|
||||
},
|
||||
documentRef: doc,
|
||||
windowRef: this.window,
|
||||
});
|
||||
|
||||
app.lastExperimentalWarningKey = null;
|
||||
app.lastExperimentalWarningAt = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
export default class NotificationController {
|
||||
constructor({
|
||||
portal,
|
||||
errorContainer,
|
||||
successContainer,
|
||||
statusContainer,
|
||||
loggers = {},
|
||||
documentRef = typeof document !== "undefined" ? document : null,
|
||||
windowRef = typeof window !== "undefined" ? window : null,
|
||||
} = {}) {
|
||||
this.portal = portal;
|
||||
this.errorContainer = errorContainer;
|
||||
this.successContainer = successContainer;
|
||||
this.statusContainer = statusContainer;
|
||||
this.statusMessage =
|
||||
this.statusContainer?.querySelector("[data-status-message]") || null;
|
||||
|
||||
// Fallback loggers if not provided
|
||||
this.userLogger = loggers.userLogger || { error: console.error, warn: console.warn, log: console.log, info: console.info };
|
||||
this.devLogger = loggers.devLogger || { error: console.error, warn: console.warn, log: console.log, info: console.info };
|
||||
|
||||
this.document = documentRef;
|
||||
this.window = windowRef;
|
||||
|
||||
this.statusAutoHideHandle = null;
|
||||
this.errorAutoHideHandle = null;
|
||||
this.successAutoHideHandle = null;
|
||||
}
|
||||
|
||||
updateNotificationPortalVisibility() {
|
||||
const portal = this.portal;
|
||||
const HTMLElementCtor =
|
||||
this.window?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!portal || !HTMLElementCtor || !(portal instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containers = [
|
||||
this.errorContainer,
|
||||
this.statusContainer,
|
||||
this.successContainer,
|
||||
];
|
||||
|
||||
const hasVisibleBanner = containers.some((container) => {
|
||||
if (!container || !(container instanceof HTMLElementCtor)) {
|
||||
return false;
|
||||
}
|
||||
return !container.classList.contains("hidden");
|
||||
});
|
||||
|
||||
portal.classList.toggle("notification-portal--active", hasVisibleBanner);
|
||||
}
|
||||
|
||||
showError(msg) {
|
||||
const container = this.errorContainer;
|
||||
const HTMLElementCtor =
|
||||
this.window?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
if (msg) {
|
||||
this.userLogger.error(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.errorAutoHideHandle) {
|
||||
this.window.clearTimeout(this.errorAutoHideHandle);
|
||||
this.errorAutoHideHandle = null;
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
// Remove any content, then hide
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a message, show it
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
this.userLogger.error(msg);
|
||||
|
||||
// Optional auto-hide after 5 seconds
|
||||
if (this.window && typeof this.window.setTimeout === "function") {
|
||||
this.errorAutoHideHandle = this.window.setTimeout(() => {
|
||||
this.errorAutoHideHandle = null;
|
||||
if (container !== this.errorContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
showStatus(msg, options = {}) {
|
||||
const container = this.statusContainer;
|
||||
const messageTarget = this.statusMessage;
|
||||
const ownerDocument =
|
||||
this.document || (typeof document !== "undefined" ? document : null);
|
||||
const defaultView =
|
||||
this.window || (typeof window !== "undefined" ? window : null);
|
||||
const clearScheduler =
|
||||
defaultView?.clearTimeout ||
|
||||
(typeof clearTimeout === "function" ? clearTimeout : null);
|
||||
const schedule =
|
||||
defaultView?.setTimeout || (typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (this.statusAutoHideHandle && typeof clearScheduler === "function") {
|
||||
clearScheduler(this.statusAutoHideHandle);
|
||||
this.statusAutoHideHandle = null;
|
||||
}
|
||||
|
||||
const HTMLElementCtor =
|
||||
defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { autoHideMs, showSpinner } =
|
||||
options && typeof options === "object" ? options : Object.create(null);
|
||||
|
||||
const shouldShowSpinner = showSpinner !== false;
|
||||
const existingSpinner = container.querySelector(".status-spinner");
|
||||
|
||||
if (shouldShowSpinner) {
|
||||
if (!(existingSpinner instanceof HTMLElementCtor)) {
|
||||
const spinner = ownerDocument?.createElement?.("span") || null;
|
||||
if (spinner) {
|
||||
spinner.className = "status-spinner";
|
||||
spinner.setAttribute("aria-hidden", "true");
|
||||
if (messageTarget && container.contains(messageTarget)) {
|
||||
container.insertBefore(spinner, messageTarget);
|
||||
} else {
|
||||
container.insertBefore(spinner, container.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (existingSpinner instanceof HTMLElementCtor) {
|
||||
existingSpinner.remove();
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = "";
|
||||
}
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = msg;
|
||||
} else {
|
||||
container.textContent = msg;
|
||||
}
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
if (
|
||||
Number.isFinite(autoHideMs) &&
|
||||
autoHideMs > 0 &&
|
||||
typeof schedule === "function"
|
||||
) {
|
||||
const expectedText =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget.textContent
|
||||
: container.textContent;
|
||||
this.statusAutoHideHandle = schedule(() => {
|
||||
this.statusAutoHideHandle = null;
|
||||
const activeContainer = this.statusContainer;
|
||||
if (activeContainer !== container) {
|
||||
return;
|
||||
}
|
||||
const activeMessageTarget =
|
||||
this.statusMessage && typeof this.statusMessage.textContent === "string"
|
||||
? this.statusMessage
|
||||
: activeContainer;
|
||||
if (activeMessageTarget.textContent !== expectedText) {
|
||||
return;
|
||||
}
|
||||
if (activeMessageTarget === this.statusMessage) {
|
||||
this.statusMessage.textContent = "";
|
||||
} else {
|
||||
activeContainer.textContent = "";
|
||||
}
|
||||
activeContainer.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, autoHideMs);
|
||||
}
|
||||
}
|
||||
|
||||
showSuccess(msg) {
|
||||
const container = this.successContainer;
|
||||
const HTMLElementCtor =
|
||||
this.window?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.successAutoHideHandle) {
|
||||
this.window.clearTimeout(this.successAutoHideHandle);
|
||||
this.successAutoHideHandle = null;
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
if (this.window && typeof this.window.setTimeout === "function") {
|
||||
this.successAutoHideHandle = this.window.setTimeout(() => {
|
||||
this.successAutoHideHandle = null;
|
||||
if (container !== this.successContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.window && typeof this.window.clearTimeout === "function") {
|
||||
if (this.statusAutoHideHandle) {
|
||||
this.window.clearTimeout(this.statusAutoHideHandle);
|
||||
this.statusAutoHideHandle = null;
|
||||
}
|
||||
if (this.errorAutoHideHandle) {
|
||||
this.window.clearTimeout(this.errorAutoHideHandle);
|
||||
this.errorAutoHideHandle = null;
|
||||
}
|
||||
if (this.successAutoHideHandle) {
|
||||
this.window.clearTimeout(this.successAutoHideHandle);
|
||||
this.successAutoHideHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.portal = null;
|
||||
this.errorContainer = null;
|
||||
this.successContainer = null;
|
||||
this.statusContainer = null;
|
||||
this.statusMessage = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import NotificationController from '../../js/ui/notificationController.js';
|
||||
|
||||
// Simple mock for HTMLElement
|
||||
class MockHTMLElement {
|
||||
constructor() {
|
||||
this.classList = {
|
||||
contains: mock.fn(() => false),
|
||||
add: mock.fn(),
|
||||
remove: mock.fn(),
|
||||
toggle: mock.fn(),
|
||||
};
|
||||
this.textContent = '';
|
||||
this.querySelector = mock.fn(() => null);
|
||||
this.contains = mock.fn(() => false);
|
||||
this.insertBefore = mock.fn();
|
||||
this.remove = mock.fn();
|
||||
this.firstChild = null;
|
||||
this.setAttribute = mock.fn();
|
||||
}
|
||||
}
|
||||
|
||||
describe('NotificationController', () => {
|
||||
let controller;
|
||||
let mockPortal;
|
||||
let mockErrorContainer;
|
||||
let mockSuccessContainer;
|
||||
let mockStatusContainer;
|
||||
let mockWindow;
|
||||
let mockDocument;
|
||||
let mockUserLogger;
|
||||
let mockDevLogger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPortal = new MockHTMLElement();
|
||||
mockErrorContainer = new MockHTMLElement();
|
||||
mockSuccessContainer = new MockHTMLElement();
|
||||
mockStatusContainer = new MockHTMLElement();
|
||||
|
||||
mockWindow = {
|
||||
HTMLElement: MockHTMLElement,
|
||||
setTimeout: mock.fn((cb, ms) => setTimeout(cb, ms)),
|
||||
clearTimeout: mock.fn((id) => clearTimeout(id)),
|
||||
};
|
||||
|
||||
mockDocument = {
|
||||
createElement: mock.fn(() => new MockHTMLElement()),
|
||||
};
|
||||
|
||||
mockUserLogger = { error: mock.fn() };
|
||||
mockDevLogger = { log: mock.fn() };
|
||||
|
||||
controller = new NotificationController({
|
||||
portal: mockPortal,
|
||||
errorContainer: mockErrorContainer,
|
||||
successContainer: mockSuccessContainer,
|
||||
statusContainer: mockStatusContainer,
|
||||
loggers: { userLogger: mockUserLogger, devLogger: mockDevLogger },
|
||||
documentRef: mockDocument,
|
||||
windowRef: mockWindow,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
controller.destroy();
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it('should instantiate correctly', () => {
|
||||
assert.strictEqual(controller.portal, mockPortal);
|
||||
assert.strictEqual(controller.errorContainer, mockErrorContainer);
|
||||
});
|
||||
|
||||
it('showError should update text content and show container', () => {
|
||||
const msg = 'Test Error';
|
||||
controller.showError(msg);
|
||||
|
||||
assert.strictEqual(mockErrorContainer.textContent, msg);
|
||||
assert.strictEqual(mockErrorContainer.classList.remove.mock.calls.length, 1);
|
||||
assert.strictEqual(mockErrorContainer.classList.remove.mock.calls[0].arguments[0], 'hidden');
|
||||
assert.strictEqual(mockUserLogger.error.mock.calls.length, 1);
|
||||
assert.strictEqual(mockUserLogger.error.mock.calls[0].arguments[0], msg);
|
||||
});
|
||||
|
||||
it('showError should hide container when msg is empty', () => {
|
||||
controller.showError('');
|
||||
|
||||
assert.strictEqual(mockErrorContainer.textContent, '');
|
||||
assert.strictEqual(mockErrorContainer.classList.add.mock.calls.length, 1);
|
||||
assert.strictEqual(mockErrorContainer.classList.add.mock.calls[0].arguments[0], 'hidden');
|
||||
});
|
||||
|
||||
it('updateNotificationPortalVisibility should toggle active class on portal', () => {
|
||||
// Simulate error container being visible
|
||||
mockErrorContainer.classList.contains = mock.fn((cls) => cls !== 'hidden');
|
||||
|
||||
controller.updateNotificationPortalVisibility();
|
||||
|
||||
assert.strictEqual(mockPortal.classList.toggle.mock.calls.length, 1);
|
||||
assert.strictEqual(mockPortal.classList.toggle.mock.calls[0].arguments[0], 'notification-portal--active');
|
||||
assert.strictEqual(mockPortal.classList.toggle.mock.calls[0].arguments[1], true);
|
||||
});
|
||||
|
||||
it('showSuccess should update text content and show container', () => {
|
||||
const msg = 'Success!';
|
||||
controller.showSuccess(msg);
|
||||
|
||||
assert.strictEqual(mockSuccessContainer.textContent, msg);
|
||||
assert.strictEqual(mockSuccessContainer.classList.remove.mock.calls.length, 1);
|
||||
assert.strictEqual(mockSuccessContainer.classList.remove.mock.calls[0].arguments[0], 'hidden');
|
||||
});
|
||||
|
||||
it('showStatus should update text content and show container', () => {
|
||||
const msg = 'Status update';
|
||||
controller.showStatus(msg);
|
||||
|
||||
assert.strictEqual(mockStatusContainer.textContent, msg);
|
||||
assert.strictEqual(mockStatusContainer.classList.remove.mock.calls.length, 1);
|
||||
assert.strictEqual(mockStatusContainer.classList.remove.mock.calls[0].arguments[0], 'hidden');
|
||||
});
|
||||
|
||||
it('showStatus should handle spinner option', () => {
|
||||
const msg = 'Loading...';
|
||||
const mockSpinner = new MockHTMLElement();
|
||||
mockDocument.createElement = mock.fn(() => mockSpinner);
|
||||
|
||||
controller.showStatus(msg, { showSpinner: true });
|
||||
|
||||
assert.strictEqual(mockDocument.createElement.mock.calls.length, 1);
|
||||
assert.strictEqual(mockStatusContainer.insertBefore.mock.calls.length, 1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user