Files
marco/tests/integration/components/place-details-test.gjs
T
raucao 77db625b11
CI / Lint (pull_request) Successful in 1m3s
CI / Test (pull_request) Successful in 1m14s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
Fix WhatsApp links/numbers
Our links would work after opening WhatsApp, but not render e.g. known
business names associated with the number on the intermediate page that
you open the app or web app from
2026-08-19 12:39:50 -06:00

415 lines
12 KiB
Plaintext

import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers';
import { render, click } from '@ember/test-helpers';
import Service from '@ember/service';
import PlaceDetails from 'marco/components/place-details';
module('Integration | Component | place-details', function (hooks) {
setupRenderingTest(hooks);
class StorageService extends Service {
lists = [
{ id: 'to-go', title: 'Want to go', color: '#2e9e4f' },
{ id: 'to-do', title: 'To do', color: '#2a7fff' },
];
isPlaceSaved() {
return false;
}
findPlaceById() {
return null;
}
async storePlace(place) {
return { ...place, id: '123', createdAt: new Date().toISOString() };
}
async removePlace() {
return true;
}
async togglePlaceList() {
return true;
}
}
hooks.beforeEach(function () {
this.owner.register('service:storage', StorageService);
// Mock Router for all tests
class MockRouter extends Service {
transitionTo() {}
}
this.owner.register('service:router', MockRouter);
});
test('it formats coordinates correctly', async function (assert) {
const place = {
title: 'Test Place',
lat: 52.520006789,
lon: 13.404954123,
description: 'A place for testing.',
};
await render(<template><PlaceDetails @place={{place}} /></template>);
assert.dom('.place-details').exists();
assert.dom('.place-details h3').hasText('Test Place');
// Check for the formatted coordinates link text
// "52.520007, 13.404954" (rounded)
assert.dom('.meta-info a[href*="geo:"]').hasText('52.520007, 13.404954');
});
test('it handles missing coordinates gracefully', async function (assert) {
const place = {
title: 'Place without Coords',
};
await render(<template><PlaceDetails @place={{place}} /></template>);
assert.dom('.place-details h3').hasText('Place without Coords');
assert.dom('.meta-info a[href*="geo:"]').doesNotExist();
});
test('it reveals the list manager when save is clicked', async function (assert) {
const place = {
title: 'Cool Cafe',
lat: 10,
lon: 10,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Manager is initially hidden
assert.dom('.place-lists-manager').doesNotExist();
// Find the Save button
// It's the first button in .actions
const saveBtn = this.element.querySelector('.actions button');
await click(saveBtn);
// Manager should be visible now
assert.dom('.place-lists-manager').exists();
// Check for default lists from mock service
assert.dom('.place-lists-manager').includesText('Want to go');
assert.dom('.place-lists-manager').includesText('To do');
assert.dom('.place-lists-manager').includesText('Saved');
});
test('it handles saving a new place via master toggle', async function (assert) {
let storedPlace = null;
// Override mock service specifically for this test to spy on storePlace
class MockStorage extends Service {
lists = [];
async storePlace(place) {
storedPlace = place;
return { ...place, id: 'new-id', createdAt: new Date().toISOString() };
}
isPlaceSaved() {
return false;
}
findPlaceById() {
return null;
}
}
this.owner.register('service:storage', MockStorage);
const place = {
title: 'New Spot',
lat: 20,
lon: 20,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Open manager
await click('.actions button');
// Find master "Saved" toggle
const masterToggle = this.element.querySelector(
'.place-lists-manager .master-toggle input'
);
// It should be unchecked initially for a new place
assert.dom(masterToggle).isNotChecked();
// Click it to save
await click(masterToggle);
// Verify storePlace was called
assert.ok(storedPlace, 'storePlace was called');
assert.strictEqual(storedPlace.title, 'New Spot');
});
test('it handles removing a saved place via master toggle', async function (assert) {
let removedPlaceId = null;
class MockStorage extends Service {
lists = [];
async removePlace(place) {
removedPlaceId = place.id;
}
isPlaceSaved() {
return true;
}
}
this.owner.register('service:storage', MockStorage);
const place = {
id: 'saved-id',
title: 'Saved Spot',
lat: 30,
lon: 30,
createdAt: '2023-01-01', // Marks it as saved
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Open manager
await click('.actions button');
// Find master "Saved" toggle
const masterToggle = this.element.querySelector(
'.place-lists-manager .master-toggle input'
);
// It should be checked initially for a saved place
assert.dom(masterToggle).isChecked();
// Click it to remove
await click(masterToggle);
assert.strictEqual(removedPlaceId, 'saved-id', 'removePlace was called');
assert.deepEqual(place._listIds, [], '_listIds was cleared on the object');
});
test('it adds place to a list', async function (assert) {
let listId = null;
let placeArg = null;
let shouldAdd = null;
class MockStorage extends Service {
lists = [{ id: 'favs', title: 'Favorites', color: 'red' }];
async togglePlaceList(place, id, add) {
placeArg = place;
listId = id;
shouldAdd = add;
}
isPlaceSaved() {
return true;
}
}
this.owner.register('service:storage', MockStorage);
// Provide a place that is already saved
const place = {
id: 'p1',
title: 'My Spot',
createdAt: '2023-01-01',
_listIds: [], // Not in any list yet
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Open manager
await click('.actions button');
// Find the checkbox for "Favorites"
const checkbox = this.element.querySelectorAll(
'.place-lists-manager input[type="checkbox"]'
)[1]; // Index 1 because 0 is master toggle
await click(checkbox);
assert.strictEqual(listId, 'favs');
assert.strictEqual(placeArg.id, 'p1');
assert.true(shouldAdd);
});
test('it respects storage service state over stale place object', async function (assert) {
class MockStorage extends Service {
lists = [];
isPlaceSaved() {
return false;
}
findPlaceById() {
return null;
}
}
this.owner.register('service:storage', MockStorage);
const place = {
id: 'stale-id',
title: 'Stale Place',
createdAt: '2023-01-01', // Looks saved
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Button should say "Save", not "Saved" because isPlaceSaved returns false
assert.dom('.actions button').hasText('Save');
assert.dom('.actions button').doesNotHaveClass('btn-secondary');
});
test('it aggregates phone and mobile tags without duplicates', async function (assert) {
const place = {
title: 'Phone Shop',
osmTags: {
phone: '+1-234-567-8900',
'contact:phone': '+1-234-567-8900; +1 000 000 0000',
mobile: '+1 987 654 3210',
'contact:mobile': '+1 987 654 3210',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Use specific selector for the phone block since there's no cuisine or opening_hours
const metaInfos = Array.from(
this.element.querySelectorAll('.meta-info .content-with-icon')
);
const phoneBlock = metaInfos.find((el) => {
const iconSpan = el.querySelector('span.icon[title="Phone"]');
return !!iconSpan;
});
assert.ok(phoneBlock, 'Phone block is rendered');
const links = phoneBlock.querySelectorAll('a[href^="tel:"]');
assert.strictEqual(
links.length,
3,
'Rendered exactly 3 unique phone links'
);
assert.strictEqual(links[0].getAttribute('href'), 'tel:+12345678900');
assert.strictEqual(links[1].getAttribute('href'), 'tel:+10000000000');
assert.strictEqual(links[2].getAttribute('href'), 'tel:+19876543210');
assert.dom(links[0]).hasText('+1-234-567-8900');
assert.dom(links[1]).hasText('+1 000 000 0000');
assert.dom(links[2]).hasText('+1 987 654 3210');
});
test('it formats whatsapp tags into wa.me links', async function (assert) {
const place = {
title: 'Chat Shop',
osmTags: {
'contact:whatsapp': '+1 234-567 8900',
whatsapp: '+44 987 654 321', // Also tests multiple values
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const metaInfos = Array.from(
this.element.querySelectorAll('.meta-info .content-with-icon')
);
const whatsappBlock = metaInfos.find((el) => {
const iconSpan = el.querySelector('span.icon[title="WhatsApp"]');
return !!iconSpan;
});
assert.ok(whatsappBlock, 'WhatsApp block is rendered');
const links = whatsappBlock.querySelectorAll('a[href^="https://wa.me/"]');
assert.strictEqual(links.length, 2, 'Rendered exactly 2 WhatsApp links');
// Verify it stripped the dashes, spaces and leading plus for the wa.me URL
assert.strictEqual(
links[0].getAttribute('href'),
'https://wa.me/44987654321'
);
assert.strictEqual(
links[1].getAttribute('href'),
'https://wa.me/12345678900'
);
// Verify it kept the dashes and spaces for the visible text
assert.dom(links[0]).hasText('+44 987 654 321');
assert.dom(links[1]).hasText('+1 234-567 8900');
});
test('it strips parentheses, dots and the leading plus from whatsapp hrefs', async function (assert) {
const place = {
title: 'Chat Shop',
osmTags: {
whatsapp: '+504-9850-3802;(504) 9850.3802;+1.234.567.8900',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const links = this.element.querySelectorAll('a[href^="https://wa.me/"]');
assert.strictEqual(links.length, 3, 'Rendered exactly 3 WhatsApp links');
assert.strictEqual(
links[0].getAttribute('href'),
'https://wa.me/50498503802'
);
assert.strictEqual(
links[1].getAttribute('href'),
'https://wa.me/50498503802'
);
assert.strictEqual(
links[2].getAttribute('href'),
'https://wa.me/12345678900'
);
});
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
const place = {
title: 'OSM Place',
osmId: '12345',
osmType: 'node',
lat: 52.520008,
lon: 13.404954,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const osmLink = this.element.querySelector(
'.meta-info a[href^="https://www.openstreetmap.org/node/12345"]'
);
assert.ok(osmLink, 'OpenStreetMap link is rendered');
assert.strictEqual(
osmLink.getAttribute('href'),
'https://www.openstreetmap.org/node/12345'
);
assert.dom('button.btn-link').hasText('Add a photo');
});
test('it renders correct search-based OpenStreetMap link for a custom saved place', async function (assert) {
class MockMapUi extends Service {
currentCenter = { lat: 52.5, lon: 13.4 };
currentZoom = 15.6;
}
this.owner.register('service:map-ui', MockMapUi);
const place = {
title: 'Custom Place',
lat: 52.520008,
lon: 13.404954,
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const osmLink = this.element.querySelector(
'.meta-info a[href^="https://www.openstreetmap.org/search"]'
);
assert.ok(osmLink, 'OpenStreetMap search link is rendered');
assert.strictEqual(
osmLink.getAttribute('href'),
'https://www.openstreetmap.org/search?lat=52.520008&lon=13.404954&zoom=16#map=16/52.50000/13.40000'
);
assert.dom('button.btn-link').doesNotExist();
});
});