mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-10 15:13:32 -07:00
fix(notifications): show thumbnail image in overlay mined-card notificat
- Share generated notification icon between overlay and system notification paths - Add `image` field to OverlayNotificationPayload; render as IMG with has-image layout - Widen overlay stack to 420px; enlarge card padding and min-height for image variant - Show OSD message after successful anilist retry when attempt key already handled
This commit is contained in:
@@ -1,12 +1,94 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
createOverlayNotificationRenderer,
|
||||
createOverlayNotificationStore,
|
||||
handleOverlayNotificationEvent,
|
||||
overlayNotificationPositionClass,
|
||||
} from './overlay-notifications';
|
||||
|
||||
function createClassList(initialTokens: string[] = []) {
|
||||
const tokens = new Set(initialTokens);
|
||||
return {
|
||||
add: (...entries: string[]) => {
|
||||
for (const entry of entries) tokens.add(entry);
|
||||
},
|
||||
remove: (...entries: string[]) => {
|
||||
for (const entry of entries) tokens.delete(entry);
|
||||
},
|
||||
contains: (entry: string) => tokens.has(entry),
|
||||
toggle: (entry: string, force?: boolean) => {
|
||||
if (force === true) tokens.add(entry);
|
||||
else if (force === false) tokens.delete(entry);
|
||||
else if (tokens.has(entry)) tokens.delete(entry);
|
||||
else tokens.add(entry);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type FakeElement = {
|
||||
tagName: string;
|
||||
className: string;
|
||||
textContent: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
type: string;
|
||||
dataset: Record<string, string>;
|
||||
children: FakeElement[];
|
||||
classList: ReturnType<typeof createClassList>;
|
||||
append: (...children: FakeElement[]) => void;
|
||||
replaceChildren: (...children: FakeElement[]) => void;
|
||||
setAttribute: (name: string, value: string) => void;
|
||||
getAttribute: (name: string) => string | null;
|
||||
addEventListener: (type: string, listener: (event?: unknown) => void) => void;
|
||||
};
|
||||
|
||||
function createFakeElement(tagName = 'div'): FakeElement {
|
||||
const attributes = new Map<string, string>();
|
||||
const element: FakeElement = {
|
||||
tagName: tagName.toUpperCase(),
|
||||
className: '',
|
||||
textContent: '',
|
||||
src: '',
|
||||
alt: '',
|
||||
type: '',
|
||||
dataset: {},
|
||||
children: [],
|
||||
classList: createClassList(),
|
||||
append: (...children) => {
|
||||
element.children.push(...children);
|
||||
},
|
||||
replaceChildren: (...children) => {
|
||||
element.children = [...children];
|
||||
},
|
||||
setAttribute: (name, value) => {
|
||||
attributes.set(name, value);
|
||||
},
|
||||
getAttribute: (name) => attributes.get(name) ?? null,
|
||||
addEventListener: () => undefined,
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
||||
function findChildByClass(element: FakeElement, className: string): FakeElement | null {
|
||||
if (element.className.split(/\s+/).includes(className)) {
|
||||
return element;
|
||||
}
|
||||
for (const child of element.children) {
|
||||
const match = findChildByClass(child, className);
|
||||
if (match) return match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const overlayNotificationCss = readFileSync(
|
||||
path.join(process.cwd(), 'src/renderer/style.css'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('overlay notification store caps transient notifications and keeps pinned jobs visible', () => {
|
||||
const store = createOverlayNotificationStore({ maxVisible: 3 });
|
||||
|
||||
@@ -63,3 +145,68 @@ test('overlay notification event handler dismisses notifications by id', () => {
|
||||
|
||||
assert.deepEqual(calls, ['remove:overlay-loading-status']);
|
||||
});
|
||||
|
||||
test('overlay notification renderer shows thumbnail image from payload', () => {
|
||||
const originalDocument = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
||||
const stack = createFakeElement();
|
||||
|
||||
Object.defineProperty(globalThis, 'document', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
createElement: (tagName: string) => createFakeElement(tagName),
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const renderer = createOverlayNotificationRenderer({
|
||||
dom: {
|
||||
overlayNotificationStack: stack,
|
||||
},
|
||||
state: {
|
||||
isOverOverlayNotification: false,
|
||||
},
|
||||
} as never);
|
||||
|
||||
renderer.show({
|
||||
title: 'Anki Card Updated',
|
||||
body: 'Updated card: 食べる',
|
||||
image: 'file:///tmp/subminer-notification-icon.png',
|
||||
variant: 'success',
|
||||
persistent: true,
|
||||
});
|
||||
|
||||
const card = stack.children[0];
|
||||
if (!card) {
|
||||
assert.fail('Expected overlay notification card.');
|
||||
}
|
||||
const image = findChildByClass(card, 'overlay-notification-image');
|
||||
if (!image) {
|
||||
assert.fail('Expected overlay notification image.');
|
||||
}
|
||||
|
||||
assert.equal(image.tagName, 'IMG');
|
||||
assert.equal(image.src, 'file:///tmp/subminer-notification-icon.png');
|
||||
assert.equal(image.alt, '');
|
||||
} finally {
|
||||
if (originalDocument) {
|
||||
Object.defineProperty(globalThis, 'document', originalDocument);
|
||||
} else {
|
||||
delete (globalThis as { document?: unknown }).document;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('overlay notification cards use larger display dimensions', () => {
|
||||
assert.match(
|
||||
overlayNotificationCss,
|
||||
/\.overlay-notification-stack\s*\{[^}]*width:\s*min\(420px,\s*calc\(100vw - 32px\)\);/s,
|
||||
);
|
||||
assert.match(overlayNotificationCss, /\.overlay-notification-card\s*\{[^}]*min-height:\s*76px;/s);
|
||||
assert.match(
|
||||
overlayNotificationCss,
|
||||
/\.overlay-notification-card\.has-image\s*\{[^}]*min-height:\s*88px;/s,
|
||||
);
|
||||
assert.match(overlayNotificationCss, /\.overlay-notification-image\s*\{[^}]*width:\s*56px;/s);
|
||||
assert.match(overlayNotificationCss, /\.overlay-notification-image\s*\{[^}]*height:\s*56px;/s);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user