mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
feat: refactor banner service and add comprehensive tests
- Remove legacy community support banner tracking variables and logic - Simplify banner dismissal handling by checking dismissal state before marking - Replace timer-based community support banner with immediate registration - Clean up unused constants and legacy storage keys - Add comprehensive test suite with mocked dependencies - Improve code maintainability and test coverage
This commit is contained in:
@@ -12,7 +12,6 @@ const COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY = 'community_support_banner_first_seen
|
|||||||
const COMMUNITY_SUPPORT_VERSION_KEY = 'community_support_banner_state_version';
|
const COMMUNITY_SUPPORT_VERSION_KEY = 'community_support_banner_state_version';
|
||||||
// Increment this version to reset the banner schedule after significant updates
|
// Increment this version to reset the banner schedule after significant updates
|
||||||
const COMMUNITY_SUPPORT_STATE_VERSION = 'v2';
|
const COMMUNITY_SUPPORT_STATE_VERSION = 'v2';
|
||||||
const COMMUNITY_SUPPORT_SHOWN_KEY_LEGACY = 'community_support_banner_shown';
|
|
||||||
const KO_FI_URL = 'https://ko-fi.com/pixelpawsai';
|
const KO_FI_URL = 'https://ko-fi.com/pixelpawsai';
|
||||||
const AFDIAN_URL = 'https://afdian.com/a/pixelpawsai';
|
const AFDIAN_URL = 'https://afdian.com/a/pixelpawsai';
|
||||||
const BANNER_HISTORY_KEY = 'banner_history';
|
const BANNER_HISTORY_KEY = 'banner_history';
|
||||||
@@ -28,8 +27,6 @@ class BannerService {
|
|||||||
this.banners = new Map();
|
this.banners = new Map();
|
||||||
this.container = null;
|
this.container = null;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.communitySupportBannerTimer = null;
|
|
||||||
this.communitySupportBannerRegistered = false;
|
|
||||||
this.recentHistory = this.loadBannerHistory();
|
this.recentHistory = this.loadBannerHistory();
|
||||||
this.bannerHistoryViewedAt = this.loadBannerHistoryViewedAt();
|
this.bannerHistoryViewedAt = this.loadBannerHistoryViewedAt();
|
||||||
|
|
||||||
@@ -114,7 +111,9 @@ class BannerService {
|
|||||||
*/
|
*/
|
||||||
dismissBanner(bannerId) {
|
dismissBanner(bannerId) {
|
||||||
const dismissedBanners = getStorageItem('dismissed_banners', []);
|
const dismissedBanners = getStorageItem('dismissed_banners', []);
|
||||||
if (!dismissedBanners.includes(bannerId)) {
|
let bannerAlreadyDismissed = dismissedBanners.includes(bannerId);
|
||||||
|
|
||||||
|
if (!bannerAlreadyDismissed) {
|
||||||
dismissedBanners.push(bannerId);
|
dismissedBanners.push(bannerId);
|
||||||
setStorageItem('dismissed_banners', dismissedBanners);
|
setStorageItem('dismissed_banners', dismissedBanners);
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,9 @@ class BannerService {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.markBannerDismissed(bannerId);
|
if (!bannerAlreadyDismissed) {
|
||||||
|
this.markBannerDismissed(bannerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,11 +233,6 @@ class BannerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareCommunitySupportBanner() {
|
prepareCommunitySupportBanner() {
|
||||||
if (this.communitySupportBannerTimer) {
|
|
||||||
clearTimeout(this.communitySupportBannerTimer);
|
|
||||||
this.communitySupportBannerTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isBannerDismissed(COMMUNITY_SUPPORT_BANNER_ID)) {
|
if (this.isBannerDismissed(COMMUNITY_SUPPORT_BANNER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -250,29 +246,17 @@ class BannerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const availableAt = firstSeenAt + COMMUNITY_SUPPORT_BANNER_DELAY_MS;
|
const availableAt = firstSeenAt + COMMUNITY_SUPPORT_BANNER_DELAY_MS;
|
||||||
const delay = Math.max(availableAt - now, 0);
|
|
||||||
|
if (now >= availableAt) {
|
||||||
if (delay === 0) {
|
|
||||||
this.registerCommunitySupportBanner();
|
this.registerCommunitySupportBanner();
|
||||||
} else {
|
|
||||||
this.communitySupportBannerTimer = setTimeout(() => {
|
|
||||||
this.registerCommunitySupportBanner();
|
|
||||||
}, delay);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCommunitySupportBanner() {
|
registerCommunitySupportBanner() {
|
||||||
if (this.communitySupportBannerRegistered || this.isBannerDismissed(COMMUNITY_SUPPORT_BANNER_ID)) {
|
if (this.isBannerDismissed(COMMUNITY_SUPPORT_BANNER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.communitySupportBannerTimer) {
|
|
||||||
clearTimeout(this.communitySupportBannerTimer);
|
|
||||||
this.communitySupportBannerTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.communitySupportBannerRegistered = true;
|
|
||||||
|
|
||||||
// Determine support URL based on user language
|
// Determine support URL based on user language
|
||||||
const currentLanguage = state.global.settings.language;
|
const currentLanguage = state.global.settings.language;
|
||||||
const supportUrl = currentLanguage === 'zh-CN' ? AFDIAN_URL : KO_FI_URL;
|
const supportUrl = currentLanguage === 'zh-CN' ? AFDIAN_URL : KO_FI_URL;
|
||||||
@@ -330,7 +314,6 @@ class BannerService {
|
|||||||
|
|
||||||
setStorageItem(COMMUNITY_SUPPORT_VERSION_KEY, COMMUNITY_SUPPORT_STATE_VERSION);
|
setStorageItem(COMMUNITY_SUPPORT_VERSION_KEY, COMMUNITY_SUPPORT_STATE_VERSION);
|
||||||
setStorageItem(COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY, Date.now());
|
setStorageItem(COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY, Date.now());
|
||||||
removeStorageItem(COMMUNITY_SUPPORT_SHOWN_KEY_LEGACY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBannerHistory() {
|
loadBannerHistory() {
|
||||||
|
|||||||
219
tests/frontend/managers/BannerService.test.js
Normal file
219
tests/frontend/managers/BannerService.test.js
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { bannerService } from '../../../static/js/managers/BannerService.js';
|
||||||
|
import * as storageHelpers from '../../../static/js/utils/storageHelpers.js';
|
||||||
|
import * as i18nHelpers from '../../../static/js/utils/i18nHelpers.js';
|
||||||
|
import { state } from '../../../static/js/state/index.js';
|
||||||
|
|
||||||
|
// Mock storage helpers
|
||||||
|
vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
|
||||||
|
getStorageItem: vi.fn(),
|
||||||
|
setStorageItem: vi.fn(),
|
||||||
|
removeStorageItem: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock i18n helpers
|
||||||
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
translate: vi.fn((key, params, defaultValue) => defaultValue || key)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock state
|
||||||
|
vi.mock('../../../static/js/state/index.js', () => ({
|
||||||
|
state: {
|
||||||
|
global: {
|
||||||
|
settings: {
|
||||||
|
language: 'en'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('BannerService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clear all mocks
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
// Reset banner service state
|
||||||
|
bannerService.banners.clear();
|
||||||
|
bannerService.initialized = false;
|
||||||
|
|
||||||
|
// Clear DOM
|
||||||
|
document.body.innerHTML = '<div id="banner-container"></div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Community Support Banner', () => {
|
||||||
|
const COMMUNITY_SUPPORT_BANNER_ID = 'community-support';
|
||||||
|
const COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY = 'community_support_banner_first_seen_at';
|
||||||
|
const COMMUNITY_SUPPORT_VERSION_KEY = 'community_support_banner_state_version';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the version check to avoid resetting state
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === COMMUNITY_SUPPORT_VERSION_KEY) {
|
||||||
|
return 'v2'; // Current version
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the banner service
|
||||||
|
bannerService.initializeCommunitySupportState();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show community support banner before 5 days have passed', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const firstSeenAt = now - (3 * 24 * 60 * 60 * 1000); // 3 days ago
|
||||||
|
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY) {
|
||||||
|
return firstSeenAt;
|
||||||
|
}
|
||||||
|
if (key === COMMUNITY_SUPPORT_VERSION_KEY) {
|
||||||
|
return 'v2';
|
||||||
|
}
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock Date.now to control time
|
||||||
|
const originalNow = Date.now;
|
||||||
|
global.Date.now = vi.fn(() => now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bannerService.prepareCommunitySupportBanner();
|
||||||
|
|
||||||
|
// Banner should not be registered yet
|
||||||
|
expect(bannerService.banners.has(COMMUNITY_SUPPORT_BANNER_ID)).toBe(false);
|
||||||
|
} finally {
|
||||||
|
global.Date.now = originalNow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show community support banner after 5 days have passed', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const firstSeenAt = now - (6 * 24 * 60 * 60 * 1000); // 6 days ago
|
||||||
|
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY) {
|
||||||
|
return firstSeenAt;
|
||||||
|
}
|
||||||
|
if (key === COMMUNITY_SUPPORT_VERSION_KEY) {
|
||||||
|
return 'v2';
|
||||||
|
}
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock Date.now to control time
|
||||||
|
const originalNow = Date.now;
|
||||||
|
global.Date.now = vi.fn(() => now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bannerService.prepareCommunitySupportBanner();
|
||||||
|
|
||||||
|
// Banner should be registered
|
||||||
|
expect(bannerService.banners.has(COMMUNITY_SUPPORT_BANNER_ID)).toBe(true);
|
||||||
|
} finally {
|
||||||
|
global.Date.now = originalNow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show community support banner if it has been dismissed', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const firstSeenAt = now - (6 * 24 * 60 * 60 * 1000); // 6 days ago
|
||||||
|
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY) {
|
||||||
|
return firstSeenAt;
|
||||||
|
}
|
||||||
|
if (key === COMMUNITY_SUPPORT_VERSION_KEY) {
|
||||||
|
return 'v2';
|
||||||
|
}
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return [COMMUNITY_SUPPORT_BANNER_ID]; // Dismissed
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock Date.now to control time
|
||||||
|
const originalNow = Date.now;
|
||||||
|
global.Date.now = vi.fn(() => now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bannerService.prepareCommunitySupportBanner();
|
||||||
|
|
||||||
|
// Banner should not be registered because it's dismissed
|
||||||
|
expect(bannerService.banners.has(COMMUNITY_SUPPORT_BANNER_ID)).toBe(false);
|
||||||
|
} finally {
|
||||||
|
global.Date.now = originalNow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set first seen time if not already set', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY) {
|
||||||
|
return null; // Not set
|
||||||
|
}
|
||||||
|
if (key === COMMUNITY_SUPPORT_VERSION_KEY) {
|
||||||
|
return 'v2';
|
||||||
|
}
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock Date.now to control time
|
||||||
|
const originalNow = Date.now;
|
||||||
|
global.Date.now = vi.fn(() => now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bannerService.prepareCommunitySupportBanner();
|
||||||
|
|
||||||
|
// Should have set the first seen time
|
||||||
|
expect(storageHelpers.setStorageItem).toHaveBeenCalledWith(
|
||||||
|
COMMUNITY_SUPPORT_FIRST_SEEN_AT_KEY,
|
||||||
|
now
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
global.Date.now = originalNow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Banner Dismissal', () => {
|
||||||
|
it('should add banner to dismissed_banners array when dismissed', () => {
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
bannerService.dismissBanner('test-banner');
|
||||||
|
|
||||||
|
expect(storageHelpers.setStorageItem).toHaveBeenCalledWith(
|
||||||
|
'dismissed_banners',
|
||||||
|
['test-banner']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add duplicate banner IDs to dismissed_banners array', () => {
|
||||||
|
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||||
|
if (key === 'dismissed_banners') {
|
||||||
|
return ['test-banner'];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
bannerService.dismissBanner('test-banner');
|
||||||
|
|
||||||
|
// Should not have been called again since it's already dismissed
|
||||||
|
expect(storageHelpers.setStorageItem).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user