278 lines
7.3 KiB
JavaScript
278 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 {
|
|
async getCachedOsmObject() {
|
|
return null;
|
|
}
|
|
|
|
async fetchOsmObjectsBatch() {
|
|
return new Map();
|
|
}
|
|
}
|
|
|
|
const NAME_CACHE_STORE = 'contributions-name-cache';
|
|
|
|
function flushPromises() {
|
|
return new Promise((resolve) => setTimeout(resolve, 50));
|
|
}
|
|
|
|
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);
|
|
});
|
|
|
|
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', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:999',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
await service.localForage.set(
|
|
NAME_CACHE_STORE,
|
|
'osm:node:999',
|
|
'Cached Park'
|
|
);
|
|
|
|
service._updateItems(events);
|
|
await flushPromises();
|
|
|
|
assert.strictEqual(service.items[0].placeName, 'Cached Park');
|
|
assert.false(service.items[0].placeNameLoading);
|
|
});
|
|
|
|
test('resolved names are persisted to the IndexedDB name cache', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:42',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
service.osm.fetchOsmObjectsBatch = async () => {
|
|
const map = new Map();
|
|
map.set('node:42', { title: 'Test Place' });
|
|
return map;
|
|
};
|
|
|
|
service._updateItems(events);
|
|
await flushPromises();
|
|
|
|
assert.strictEqual(
|
|
await service.localForage.get(NAME_CACHE_STORE, 'osm:node:42'),
|
|
'Test Place',
|
|
'Name is persisted to the IndexedDB name cache'
|
|
);
|
|
});
|
|
|
|
test('names stored in a previous session are resolved from the IndexedDB cache', async function (assert) {
|
|
const events = [
|
|
makePhotoEvent({
|
|
id: 'e1',
|
|
created_at: 1000,
|
|
placeIdentifier: 'osm:node:77',
|
|
}),
|
|
];
|
|
|
|
const service = this.owner.lookup('service:contributions');
|
|
await service.localForage.set(
|
|
NAME_CACHE_STORE,
|
|
'osm:node:77',
|
|
'Persisted Place'
|
|
);
|
|
|
|
service._updateItems(events);
|
|
await flushPromises();
|
|
|
|
assert.strictEqual(service.items[0].placeName, 'Persisted Place');
|
|
assert.false(service.items[0].placeNameLoading);
|
|
});
|
|
|
|
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 flushPromises();
|
|
|
|
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 flushPromises();
|
|
|
|
// Fallback should be shown but NOT cached
|
|
assert.strictEqual(service.items[0].placeName, 'OSM node 666');
|
|
assert.notOk(
|
|
await service.localForage.get(NAME_CACHE_STORE, 'osm:node:666'),
|
|
'No fallback name persisted in the IndexedDB name cache'
|
|
);
|
|
assert.true(
|
|
service._unresolvable.has('osm:node:666'),
|
|
'Item is marked unresolvable for this session'
|
|
);
|
|
});
|
|
|
|
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 flushPromises();
|
|
|
|
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 flushPromises();
|
|
|
|
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 flushPromises();
|
|
|
|
assert.strictEqual(service.items[0].placeName, 'Resolved Café');
|
|
assert.strictEqual(
|
|
await service.localForage.get(NAME_CACHE_STORE, 'osm:node:111'),
|
|
'Resolved Café',
|
|
'Name is stored in the name cache'
|
|
);
|
|
});
|
|
});
|