mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
feat(banners): rotate active banners one at a time with a pager
Stacking every active banner vertically ate header height when several were active at once. Only the highest-priority banner renders now; a ‹ 1/N › pager cycles through the rest, and all active banners are still recorded in the notification-center history so cycled-away ones stay reachable. Newly registered banners preempt the displayed one only when they outrank it. Also fix the startup flow: the restart-required banner (now priority 80) outranks the model-folders setup warning (60), and the setup banner is retired once a non-empty folder path is saved. New banners.pager.* keys translated in all 9 locales.
This commit is contained in:
@@ -43,6 +43,7 @@ describe('BannerService', () => {
|
||||
// Reset banner service state
|
||||
bannerService.banners.clear();
|
||||
bannerService.initialized = false;
|
||||
bannerService.currentBannerIndex = 0;
|
||||
bannerService.recentHistory = []; // Clear history for each test
|
||||
|
||||
// Clear DOM
|
||||
@@ -331,6 +332,116 @@ describe('BannerService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Banner Rotation', () => {
|
||||
const registerTestBanner = (id, priority) => {
|
||||
bannerService.registerBanner(id, {
|
||||
id,
|
||||
title: `Banner ${id}`,
|
||||
content: `Content ${id}`,
|
||||
dismissible: true,
|
||||
priority
|
||||
});
|
||||
};
|
||||
|
||||
const displayedBannerId = () =>
|
||||
document.querySelector('#banner-container .banner-item')
|
||||
?.getAttribute('data-banner-id');
|
||||
|
||||
let dismissedStore;
|
||||
|
||||
beforeEach(() => {
|
||||
dismissedStore = [];
|
||||
storageHelpers.getStorageItem.mockImplementation((key, defaultValue) => {
|
||||
if (key === 'dismissed_banners') {
|
||||
return dismissedStore;
|
||||
}
|
||||
return defaultValue;
|
||||
});
|
||||
storageHelpers.setStorageItem.mockImplementation((key, value) => {
|
||||
if (key === 'dismissed_banners') {
|
||||
dismissedStore = value;
|
||||
}
|
||||
});
|
||||
bannerService.container = document.getElementById('banner-container');
|
||||
bannerService.initialized = true;
|
||||
});
|
||||
|
||||
it('renders only the highest priority banner when multiple are active', () => {
|
||||
registerTestBanner('low', 1);
|
||||
registerTestBanner('high', 10);
|
||||
|
||||
const rendered = document.querySelectorAll('#banner-container .banner-item');
|
||||
expect(rendered).toHaveLength(1);
|
||||
expect(displayedBannerId()).toBe('high');
|
||||
});
|
||||
|
||||
it('shows a pager with position indicator when multiple banners are active', () => {
|
||||
registerTestBanner('a', 1);
|
||||
registerTestBanner('b', 2);
|
||||
|
||||
const pager = document.querySelector('.banner-pager');
|
||||
expect(pager).not.toBeNull();
|
||||
expect(pager.querySelector('.banner-pager-indicator').textContent.trim())
|
||||
.toBe('1 / 2');
|
||||
});
|
||||
|
||||
it('does not show a pager for a single banner', () => {
|
||||
registerTestBanner('only', 1);
|
||||
|
||||
expect(document.querySelector('.banner-pager')).toBeNull();
|
||||
});
|
||||
|
||||
it('cycles to the next banner and wraps around', () => {
|
||||
registerTestBanner('a', 1);
|
||||
registerTestBanner('b', 2);
|
||||
|
||||
document.querySelector('[data-pager="next"]')
|
||||
.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
expect(displayedBannerId()).toBe('a');
|
||||
expect(document.querySelector('.banner-pager-indicator').textContent.trim())
|
||||
.toBe('2 / 2');
|
||||
|
||||
document.querySelector('[data-pager="next"]')
|
||||
.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
expect(displayedBannerId()).toBe('b');
|
||||
expect(document.querySelector('.banner-pager-indicator').textContent.trim())
|
||||
.toBe('1 / 2');
|
||||
});
|
||||
|
||||
it('cycles backwards with the previous button', () => {
|
||||
registerTestBanner('a', 1);
|
||||
registerTestBanner('b', 2);
|
||||
|
||||
document.querySelector('[data-pager="prev"]')
|
||||
.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
expect(displayedBannerId()).toBe('a');
|
||||
});
|
||||
|
||||
it('shows the next banner after the displayed one is dismissed', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
registerTestBanner('a', 1);
|
||||
registerTestBanner('b', 2);
|
||||
expect(displayedBannerId()).toBe('b');
|
||||
|
||||
await bannerService.dismissBanner('b');
|
||||
vi.advanceTimersByTime(300);
|
||||
|
||||
expect(displayedBannerId()).toBe('a');
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('records all active banners in history, not just the displayed one', () => {
|
||||
registerTestBanner('a', 1);
|
||||
registerTestBanner('b', 2);
|
||||
|
||||
const historyIds = bannerService.recentHistory.map(entry => entry.id);
|
||||
expect(historyIds).toEqual(expect.arrayContaining(['a', 'b']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Banner History', () => {
|
||||
const testBanner = {
|
||||
id: 'test-banner',
|
||||
|
||||
Reference in New Issue
Block a user