261 lines
7.3 KiB
JavaScript
261 lines
7.3 KiB
JavaScript
import { module, test } from 'qunit';
|
|
import { setupTest } from 'marco/tests/helpers';
|
|
import Service from '@ember/service';
|
|
|
|
function makePhotoEvent({
|
|
id,
|
|
created_at,
|
|
placeIdentifier = 'osm:node:123',
|
|
url = 'https://example.com/photo.jpg',
|
|
}) {
|
|
return {
|
|
id,
|
|
pubkey: 'pubkey-1',
|
|
kind: 360,
|
|
created_at,
|
|
tags: [
|
|
['i', placeIdentifier],
|
|
['imeta', `url ${url}`, 'dim 800x600'],
|
|
],
|
|
};
|
|
}
|
|
|
|
class MockStorageService extends Service {
|
|
savedPlaces = [];
|
|
findPlaceById() {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class MockNostrDataService extends Service {
|
|
store = {
|
|
timeline() {
|
|
return {
|
|
subscribe() {
|
|
return { unsubscribe() {} };
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
async loadMyContributions() {}
|
|
}
|
|
|
|
class MockOsmService extends Service {
|
|
getCachedOsmObject() {
|
|
return null;
|
|
}
|
|
|
|
async fetchOsmObjectsBatch() {
|
|
return new Map();
|
|
}
|
|
}
|
|
|
|
module('Unit | Service | contributions', function (hooks) {
|
|
setupTest(hooks);
|
|
|
|
hooks.beforeEach(function () {
|
|
this.owner.register('service:storage', MockStorageService);
|
|
this.owner.register('service:nostrData', MockNostrDataService);
|
|
this.owner.register('service:osm', MockOsmService);
|
|
|
|
localStorage.removeItem('marco:contributions:name_cache');
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
localStorage.removeItem('marco:contributions:name_cache');
|
|
});
|
|
|
|
test('_updateItems resolves names from bookmarks immediately', function (assert) {
|
|
const events = [makePhotoEvent({ id: 'e1', created_at: 1000 })];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
service.storage.savedPlaces = [{ id: '123', title: 'Bookmarked Café' }];
|
|
service.storage.findPlaceById = (id) =>
|
|
id === '123' ? { id: '123', title: 'Bookmarked Café' } : null;
|
|
|
|
service._updateItems(events);
|
|
|
|
assert.strictEqual(service.items.length, 1);
|
|
assert.false(service.items[0].placeNameLoading, 'Not loading');
|
|
assert.strictEqual(
|
|
service.items[0].placeName,
|
|
'Bookmarked Café',
|
|
'Resolved from bookmark'
|
|
);
|
|
});
|
|
|
|
test('_updateItems resolves names from the persistent name cache', function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:999',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
service._nameCache.set('osm:node:999', 'Cached Park');
|
|
|
|
service._updateItems(events);
|
|
|
|
assert.strictEqual(service.items[0].placeName, 'Cached Park');
|
|
assert.false(service.items[0].placeNameLoading);
|
|
});
|
|
|
|
test('name cache is persisted to localStorage', function (assert) {
|
|
const service = this.owner.lookup('service:contributions');
|
|
service._nameCache.set('osm:node:42', 'Test Place');
|
|
service._saveNameCache();
|
|
|
|
const raw = localStorage.getItem('marco:contributions:name_cache');
|
|
assert.ok(raw, 'Cache entry exists in localStorage');
|
|
const parsed = JSON.parse(raw);
|
|
assert.strictEqual(parsed['osm:node:42'], 'Test Place');
|
|
});
|
|
|
|
test('name cache is loaded from localStorage on construction', function (assert) {
|
|
localStorage.setItem(
|
|
'marco:contributions:name_cache',
|
|
JSON.stringify({ 'osm:node:77': 'Persisted Place' })
|
|
);
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
assert.strictEqual(
|
|
service._nameCache.get('osm:node:77'),
|
|
'Persisted Place'
|
|
);
|
|
});
|
|
|
|
test('batch fetch failure applies a fallback name so items do not stay loading', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:555',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
// OSM batch returns empty (simulating all-failed fetch)
|
|
service.osm.fetchOsmObjectsBatch = async () => new Map();
|
|
|
|
service._updateItems(events);
|
|
|
|
// Wait for the background batch to complete
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
assert.false(
|
|
service.items[0].placeNameLoading,
|
|
'Item is no longer loading after batch failure'
|
|
);
|
|
assert.strictEqual(
|
|
service.items[0].placeName,
|
|
'OSM node 555',
|
|
'Fallback name is applied'
|
|
);
|
|
});
|
|
|
|
test('fallback names are NOT persisted to the name cache (so they can be re-fetched next session)', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:666',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
service.osm.fetchOsmObjectsBatch = async () => new Map();
|
|
|
|
service._updateItems(events);
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
// Fallback should be shown but NOT cached
|
|
assert.strictEqual(service.items[0].placeName, 'OSM node 666');
|
|
assert.false(
|
|
service._nameCache.has('osm:node:666'),
|
|
'Fallback is not stored in the name cache'
|
|
);
|
|
assert.true(
|
|
service._unresolvable.has('osm:node:666'),
|
|
'Item is marked unresolvable for this session'
|
|
);
|
|
|
|
// Verify the fallback was NOT persisted to localStorage
|
|
service._saveNameCache();
|
|
const raw = localStorage.getItem('marco:contributions:name_cache');
|
|
if (raw) {
|
|
const parsed = JSON.parse(raw);
|
|
assert.notOk(
|
|
parsed['osm:node:666'],
|
|
'No fallback name persisted in localStorage'
|
|
);
|
|
} else {
|
|
assert.true(true, 'No cache entry was persisted');
|
|
}
|
|
});
|
|
|
|
test('unresolvable items are not re-fetched within the same session', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:777',
|
|
}),
|
|
];
|
|
|
|
let fetchCount = 0;
|
|
const service = this.owner.lookup('service:contributions');
|
|
service.osm.fetchOsmObjectsBatch = async () => {
|
|
fetchCount++;
|
|
return new Map();
|
|
};
|
|
|
|
// First update triggers a fetch
|
|
service._updateItems(events);
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
assert.strictEqual(fetchCount, 1, 'First update triggers a fetch');
|
|
assert.strictEqual(service.items[0].placeName, 'OSM node 777');
|
|
|
|
// Second update should NOT trigger another fetch (already unresolvable)
|
|
service._updateItems(events);
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
assert.strictEqual(fetchCount, 1, 'Second update does not re-fetch');
|
|
assert.strictEqual(
|
|
service.items[0].placeName,
|
|
'OSM node 777',
|
|
'Fallback is shown immediately'
|
|
);
|
|
});
|
|
|
|
test('successful batch resolution stores names in the name cache', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:111',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
service.osm.fetchOsmObjectsBatch = async () => {
|
|
const map = new Map();
|
|
map.set('node:111', { title: 'Resolved Café' });
|
|
return map;
|
|
};
|
|
|
|
service._updateItems(events);
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
assert.strictEqual(service.items[0].placeName, 'Resolved Café');
|
|
assert.strictEqual(
|
|
service._nameCache.get('osm:node:111'),
|
|
'Resolved Café',
|
|
'Name is stored in the name cache'
|
|
);
|
|
});
|
|
});
|