@@ -0,0 +1,76 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const path = require('node:path');
|
||||
|
||||
function deferred() {
|
||||
let resolve, reject;
|
||||
const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const context = vm.createContext({ document: { addEventListener() {}, getElementById() {} },
|
||||
window: {}, console: { error() {} }, escapeHtml: value => value });
|
||||
const Manager = vm.runInContext(fs.readFileSync(path.join(__dirname, '../../web/js/gallery.js'), 'utf8') + '\nGalleryManager;', context);
|
||||
const manager = Object.create(Manager.prototype);
|
||||
const requests = new Map();
|
||||
manager.extractImageMetadata = url => {
|
||||
const pending = deferred();
|
||||
requests.set(url, pending);
|
||||
return pending.promise;
|
||||
};
|
||||
manager.displayMetadata = (metadata, panel) => {
|
||||
panel.innerHTML = metadata?.positivePrompt || 'No metadata';
|
||||
manager.currentMetadata = metadata;
|
||||
};
|
||||
manager.stopViewerSlideshow = () => {};
|
||||
return { manager, requests, panel: { innerHTML: '', isConnected: true } };
|
||||
}
|
||||
|
||||
test('a late response cannot overwrite the selected image or copy state', async () => {
|
||||
const { manager, requests, panel } = fixture();
|
||||
const first = manager.loadImageMetadata('old.png', panel);
|
||||
const second = manager.loadImageMetadata('new.png', panel);
|
||||
requests.get('new.png').resolve({ positivePrompt: 'new prompt' });
|
||||
await second;
|
||||
requests.get('old.png').resolve({ positivePrompt: 'old prompt' });
|
||||
await first;
|
||||
assert.equal(panel.innerHTML, 'new prompt');
|
||||
assert.equal(manager.currentMetadata.positivePrompt, 'new prompt');
|
||||
});
|
||||
|
||||
test('navigation clears old action data while the new request is pending', async () => {
|
||||
const { manager, requests, panel } = fixture();
|
||||
manager.currentMetadata = { positivePrompt: 'old prompt' };
|
||||
const pending = manager.loadImageMetadata('new.png', panel);
|
||||
assert.equal(manager.currentMetadata, null);
|
||||
requests.get('new.png').resolve(null);
|
||||
await pending;
|
||||
assert.equal(manager.currentMetadata, null);
|
||||
});
|
||||
|
||||
test('a stale error cannot replace newer metadata', async () => {
|
||||
const { manager, requests, panel } = fixture();
|
||||
const first = manager.loadImageMetadata('old.png', panel);
|
||||
const second = manager.loadImageMetadata('new.png', panel);
|
||||
requests.get('new.png').resolve({ positivePrompt: 'new prompt' });
|
||||
await second;
|
||||
requests.get('old.png').reject(new Error('old request failed'));
|
||||
await first;
|
||||
assert.equal(panel.innerHTML, 'new prompt');
|
||||
});
|
||||
|
||||
for (const close of ['destroy viewer', 'remove modal']) {
|
||||
test(`closing via ${close} prevents pending metadata from restoring action data`, async () => {
|
||||
const { manager, requests, panel } = fixture();
|
||||
const pending = manager.loadImageMetadata('old.png', panel);
|
||||
if (close === 'destroy viewer') manager.destroyViewer();
|
||||
else panel.isConnected = false;
|
||||
requests.get('old.png').resolve({ positivePrompt: 'closed prompt' });
|
||||
await pending;
|
||||
assert.equal(manager.currentMetadata, null);
|
||||
assert.notEqual(panel.innerHTML, 'closed prompt');
|
||||
});
|
||||
}
|
||||
Vendored
+25
@@ -181,3 +181,28 @@ test('touch pinch zoom and rotation remain usable', async ({ page, isMobile }) =
|
||||
await session.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
||||
await expect.poll(() => page.evaluate(() => Math.abs(gallery.viewer.imageData.rotate))).toBe(90);
|
||||
});
|
||||
|
||||
test('metadata follows the selected image when responses finish out of order', async ({ page }) => {
|
||||
await loadGallery(page);
|
||||
await page.evaluate(() => {
|
||||
delete gallery.loadImageMetadata;
|
||||
window.metadataRequests = new Map();
|
||||
gallery.extractImageMetadata = url => new Promise(resolve => metadataRequests.set(url, resolve));
|
||||
gallery.openViewer(0);
|
||||
});
|
||||
await expect.poll(() => page.evaluate(() => metadataRequests.has('http://viewer.test/original/0.svg'))).toBe(true);
|
||||
await page.evaluate(() => gallery.viewer.next());
|
||||
await expect.poll(() => page.evaluate(() => metadataRequests.has('http://viewer.test/original/1.svg'))).toBe(true);
|
||||
await page.evaluate(() => metadataRequests.get('http://viewer.test/original/1.svg')({ positivePrompt: 'Selected prompt' }));
|
||||
await expect(page.locator('#metadata-content')).toContainText('Selected prompt');
|
||||
await page.evaluate(() => metadataRequests.get('http://viewer.test/original/0.svg')({ positivePrompt: 'Stale prompt' }));
|
||||
await expect(page.locator('#metadata-content')).toContainText('Selected prompt');
|
||||
await expect(page.locator('#metadata-content')).not.toContainText('Stale prompt');
|
||||
expect(await page.evaluate(() => gallery.currentMetadata.positivePrompt)).toBe('Selected prompt');
|
||||
await page.evaluate(() => gallery.viewer.next());
|
||||
await expect.poll(() => page.evaluate(() => metadataRequests.has('http://viewer.test/original/2.svg'))).toBe(true);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.locator('#metadata-sidebar')).toHaveCount(0);
|
||||
await page.evaluate(() => metadataRequests.get('http://viewer.test/original/2.svg')({ positivePrompt: 'Closed prompt' }));
|
||||
expect(await page.evaluate(() => gallery.currentMetadata)).toBeNull();
|
||||
});
|
||||
|
||||
+11
-1
@@ -583,6 +583,8 @@
|
||||
}
|
||||
|
||||
destroyViewer({ keepSlideshow = false } = {}) {
|
||||
this.metadataRequest = null;
|
||||
this.currentMetadata = null;
|
||||
if (!keepSlideshow) this.stopViewerSlideshow();
|
||||
if (this.viewer) {
|
||||
this.viewer.clearEnforceFocus();
|
||||
@@ -647,6 +649,8 @@
|
||||
}
|
||||
},
|
||||
hide: () => {
|
||||
this.metadataRequest = null;
|
||||
this.currentMetadata = null;
|
||||
this.viewer?.clearEnforceFocus();
|
||||
this.stopViewerSlideshow();
|
||||
const sidebar = document.getElementById('metadata-sidebar');
|
||||
@@ -1189,6 +1193,11 @@
|
||||
}
|
||||
|
||||
async loadImageMetadata(imageUrl, container) {
|
||||
const request = {};
|
||||
this.metadataRequest = request;
|
||||
this.currentMetadata = null;
|
||||
container.textContent = 'Loading metadata...';
|
||||
const isCurrent = () => this.metadataRequest === request && container.isConnected !== false;
|
||||
try {
|
||||
// First try to get linked prompt from database
|
||||
let databasePrompt = null;
|
||||
@@ -1254,8 +1263,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
this.displayMetadata(metadata, container);
|
||||
if (isCurrent()) this.displayMetadata(metadata, container);
|
||||
} catch (error) {
|
||||
if (!isCurrent()) return;
|
||||
console.error('Error loading metadata:', error);
|
||||
container.innerHTML = `
|
||||
<div class="bg-pm-error-tint border border-pm-error rounded-pm-md p-4">
|
||||
|
||||
Reference in New Issue
Block a user