Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
081138bfc0
|
||
|
|
1a490c5d04
|
||
|
|
f7eacf58d1
|
||
|
|
c8b9ddc1e5
|
||
|
|
e9be615c9a
|
+20
-1
@@ -2,6 +2,16 @@ import Component from '@glimmer/component';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
import { getIcon, isIconFilled } from '../utils/icons';
|
||||
|
||||
function formatDimension(dim) {
|
||||
if (typeof dim === 'number') {
|
||||
return `${dim}px`;
|
||||
}
|
||||
if (typeof dim === 'string' && /^\d+(\.\d+)?$/.test(dim.trim())) {
|
||||
return `${dim.trim()}px`;
|
||||
}
|
||||
return dim;
|
||||
}
|
||||
|
||||
export default class IconComponent extends Component {
|
||||
get svg() {
|
||||
return getIcon(this.args.name);
|
||||
@@ -11,13 +21,21 @@ export default class IconComponent extends Component {
|
||||
return this.args.size || 16;
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this.args.width !== undefined ? this.args.width : this.size;
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this.args.height !== undefined ? this.args.height : this.size;
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.args.color || '#898989';
|
||||
}
|
||||
|
||||
get style() {
|
||||
return htmlSafe(
|
||||
`width:${this.size}px;height:${this.size}px;color:${this.color}`
|
||||
`width:${formatDimension(this.width)};height:${formatDimension(this.height)};color:${this.color}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +53,7 @@ export default class IconComponent extends Component {
|
||||
class="icon {{if this.isFilled 'icon-filled'}}"
|
||||
style={{this.style}}
|
||||
title={{this.title}}
|
||||
...attributes
|
||||
>
|
||||
{{htmlSafe this.svg}}
|
||||
</span>
|
||||
|
||||
@@ -15,6 +15,7 @@ import NostrConnect from './nostr-connect';
|
||||
import Modal from './modal';
|
||||
import PhotoCarousel from './photo-carousel';
|
||||
import PhotoGallery from './photo-gallery';
|
||||
import PlacePaymentMethods from './place-payment-methods';
|
||||
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
@@ -467,6 +468,8 @@ export default class PlaceDetails extends Component {
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<PlacePaymentMethods @tags={{this.tags}} />
|
||||
|
||||
<div class="meta-info">
|
||||
|
||||
{{#if this.cuisine}}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import Component from '@glimmer/component';
|
||||
import Icon from './icon';
|
||||
import { parsePaymentMethods } from '../utils/payment';
|
||||
import tooltip from '../modifiers/tooltip';
|
||||
|
||||
export default class PlacePaymentMethods extends Component {
|
||||
get payment() {
|
||||
return parsePaymentMethods(this.args.tags);
|
||||
}
|
||||
|
||||
get methods() {
|
||||
const list = [];
|
||||
if (this.payment.cash !== null) {
|
||||
const isDenied = this.payment.cash === 'denied';
|
||||
list.push({
|
||||
id: 'cash',
|
||||
isDenied,
|
||||
icon: 'banknote',
|
||||
color: 'currentColor',
|
||||
description: isDenied ? 'No cash' : 'Cash accepted',
|
||||
hasBadge: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.payment.cards !== null) {
|
||||
const isDenied = this.payment.cards === 'denied';
|
||||
list.push({
|
||||
id: 'cards',
|
||||
isDenied,
|
||||
icon: 'payment-card',
|
||||
color: 'currentColor',
|
||||
description: isDenied ? 'No cards' : 'Cards accepted',
|
||||
hasBadge: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.payment.bitcoin.status !== null) {
|
||||
const isDenied = this.payment.bitcoin.status === 'denied';
|
||||
let description = 'No Bitcoin';
|
||||
if (!isDenied) {
|
||||
description = this.payment.bitcoin.lightning
|
||||
? 'Bitcoin (Lightning) accepted'
|
||||
: 'Bitcoin (On-chain) accepted';
|
||||
}
|
||||
list.push({
|
||||
id: 'bitcoin',
|
||||
isDenied,
|
||||
icon: 'bitcoin',
|
||||
width: 17,
|
||||
height: 22,
|
||||
color: 'currentColor',
|
||||
description,
|
||||
hasBadge: !isDenied && this.payment.bitcoin.lightning,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{! template-lint-disable no-unsupported-role-attributes }}
|
||||
{{#if this.payment.hasPaymentInfo}}
|
||||
<div class="place-payment-methods" aria-label="Payment methods">
|
||||
{{#each this.methods as |method|}}
|
||||
<span
|
||||
class="payment-method {{if method.isDenied 'is-denied'}}"
|
||||
data-test-payment-method={{method.id}}
|
||||
aria-label={{method.description}}
|
||||
aria-description={{method.description}}
|
||||
tabindex="0"
|
||||
{{tooltip}}
|
||||
>
|
||||
<Icon
|
||||
@name={{method.icon}}
|
||||
@size={{22}}
|
||||
@width={{method.width}}
|
||||
@height={{method.height}}
|
||||
@color={{method.color}}
|
||||
/>
|
||||
{{#if method.hasBadge}}
|
||||
<span
|
||||
class="payment-method-badge"
|
||||
data-test-payment-badge="lightning"
|
||||
>
|
||||
<Icon @name="zap" @size={{13}} @color="#fff" @filled={{true}} />
|
||||
</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="8.157 6 15.088 19.949">
|
||||
<path d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 872 B |
@@ -1175,6 +1175,71 @@ abbr[title] {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.place-payment-methods {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1.2rem;
|
||||
padding-top: 1.2rem;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.payment-method {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
outline-offset: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.payment-method.is-denied {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.payment-method.is-denied::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 28px;
|
||||
height: 2px;
|
||||
background-color: var(--danger-color);
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
pointer-events: none;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.payment-method-badge {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: var(--default-list-color);
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.payment-method-badge .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.payment-method-badge .icon svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn {
|
||||
/* TODO: remove this scoped rule in favor of a global box-sizing reset
|
||||
(e.g. `*, *::before, *::after { box-sizing: border-box; }`) at the top
|
||||
|
||||
@@ -113,6 +113,7 @@ import molarTooth from '@waysidemapping/pinhead/dist/icons/molar_tooth.svg?raw';
|
||||
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
|
||||
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
|
||||
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
|
||||
import paymentCard from '@waysidemapping/pinhead/dist/icons/payment_card.svg?raw';
|
||||
import parkingP from '@waysidemapping/pinhead/dist/icons/p_wide.svg?raw';
|
||||
import personCricketBattingAtCricketBall from '@waysidemapping/pinhead/dist/icons/person_cricket_batting_at_cricket_ball.svg?raw';
|
||||
import personBoardingTramWithDestinationDisplayAndPantographOnTramTrack from '@waysidemapping/pinhead/dist/icons/person_boarding_tram_with_destination_display_and_pantograph_on_tram_track.svg?raw';
|
||||
@@ -148,6 +149,7 @@ import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/wome
|
||||
/*
|
||||
* Custom/local icons
|
||||
*/
|
||||
import bitcoin from '../icons/bitcoin.svg?raw';
|
||||
import loadingRing from '../icons/270-ring.svg?raw';
|
||||
import nostrich from '../icons/nostrich-2.svg?raw';
|
||||
import remotestorage from '../icons/remotestorage.svg?raw';
|
||||
@@ -167,6 +169,7 @@ const ICONS = {
|
||||
'badge-shield-with-fire': badgeShieldWithFire,
|
||||
'beach-umbrella-in-ground': beachUmbrellaInGround,
|
||||
'beer-mug-with-foam': beerMugWithFoam,
|
||||
bitcoin,
|
||||
bookmark,
|
||||
'boxing-glove-up': boxingGloveUp,
|
||||
'burger-and-drink-cup-with-straw': burgerAndDrinkCupWithStraw,
|
||||
@@ -246,6 +249,7 @@ const ICONS = {
|
||||
nostrich,
|
||||
'open-book': openBook,
|
||||
palace,
|
||||
'payment-card': paymentCard,
|
||||
'person-cricket-batting-at-cricket-ball': personCricketBattingAtCricketBall,
|
||||
'person-boarding-tram-with-destination-display-and-pantograph-on-tram-track':
|
||||
personBoardingTramWithDestinationDisplayAndPantographOnTramTrack,
|
||||
@@ -303,7 +307,10 @@ const ICONS = {
|
||||
};
|
||||
|
||||
const FILLED_ICONS = [
|
||||
'banknote',
|
||||
'bitcoin',
|
||||
'fork-and-knife',
|
||||
'payment-card',
|
||||
'wikipedia',
|
||||
'whatsapp',
|
||||
'cup-and-saucer',
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
const CARD_KEYS = [
|
||||
'payment:cards',
|
||||
'payment:credit_cards',
|
||||
'payment:debit_cards',
|
||||
'payment:contactless',
|
||||
'payment:visa',
|
||||
'payment:mastercard',
|
||||
'payment:american_express',
|
||||
'payment:amex',
|
||||
'payment:maestro',
|
||||
'payment:girocard',
|
||||
'payment:discover_card',
|
||||
'payment:diners_club',
|
||||
'payment:jcb',
|
||||
'payment:unionpay',
|
||||
'payment:bancontact',
|
||||
'payment:postfinance_card',
|
||||
'payment:mir',
|
||||
'payment:dankort',
|
||||
'payment:interac',
|
||||
'payment:visa_debit',
|
||||
'payment:mastercard_debit',
|
||||
'payment:apple_pay',
|
||||
'payment:google_pay',
|
||||
];
|
||||
|
||||
function isYes(val) {
|
||||
return val === 'yes' || val === 'only';
|
||||
}
|
||||
|
||||
function isNo(val) {
|
||||
return val === 'no';
|
||||
}
|
||||
|
||||
export function parsePaymentMethods(tags = {}) {
|
||||
const safeTags = tags || {};
|
||||
|
||||
// 1. Cash
|
||||
let cash = null;
|
||||
const cashVal = safeTags['payment:cash'];
|
||||
const coinsVal = safeTags['payment:coins'];
|
||||
const notesVal = safeTags['payment:notes'];
|
||||
|
||||
if (
|
||||
isYes(cashVal) ||
|
||||
(!isNo(cashVal) && (isYes(coinsVal) || isYes(notesVal)))
|
||||
) {
|
||||
cash = 'accepted';
|
||||
} else if (
|
||||
isNo(cashVal) ||
|
||||
(!isYes(cashVal) && isNo(coinsVal) && isNo(notesVal))
|
||||
) {
|
||||
cash = 'denied';
|
||||
}
|
||||
|
||||
// 2. Cards
|
||||
let cards = null;
|
||||
const anyCardAccepted = CARD_KEYS.some((key) => isYes(safeTags[key]));
|
||||
|
||||
if (anyCardAccepted) {
|
||||
cards = 'accepted';
|
||||
} else {
|
||||
const cardsNo = isNo(safeTags['payment:cards']);
|
||||
const creditAndDebitNo =
|
||||
isNo(safeTags['payment:credit_cards']) &&
|
||||
isNo(safeTags['payment:debit_cards']);
|
||||
const cashOnly = safeTags['payment:cash'] === 'only';
|
||||
|
||||
if (cardsNo || creditAndDebitNo || cashOnly) {
|
||||
cards = 'denied';
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bitcoin
|
||||
const xbtVal = safeTags['currency:XBT'];
|
||||
const lightningVal = safeTags['payment:lightning'];
|
||||
const onchainVal = safeTags['payment:onchain'];
|
||||
const btcVal = safeTags['payment:bitcoin'];
|
||||
const btcCurrencyVal = safeTags['currency:BTC'];
|
||||
|
||||
const isBtcAccepted =
|
||||
isYes(xbtVal) ||
|
||||
isYes(lightningVal) ||
|
||||
isYes(onchainVal) ||
|
||||
isYes(btcVal) ||
|
||||
isYes(btcCurrencyVal);
|
||||
|
||||
const isBtcDenied = !isBtcAccepted && (isNo(xbtVal) || isNo(btcVal));
|
||||
|
||||
const lightning = isYes(lightningVal);
|
||||
const onchain =
|
||||
isYes(onchainVal) ||
|
||||
((isYes(xbtVal) || isYes(btcVal) || isYes(btcCurrencyVal)) &&
|
||||
!isNo(onchainVal));
|
||||
|
||||
const bitcoin = {
|
||||
status: isBtcAccepted ? 'accepted' : isBtcDenied ? 'denied' : null,
|
||||
lightning,
|
||||
onchain: isBtcAccepted ? onchain : false,
|
||||
};
|
||||
|
||||
const hasPaymentInfo =
|
||||
cash !== null || cards !== null || bitcoin.status !== null;
|
||||
|
||||
return {
|
||||
hasPaymentInfo,
|
||||
cash,
|
||||
cards,
|
||||
bitcoin,
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "marco",
|
||||
"version": "1.33.1",
|
||||
"version": "1.34.0",
|
||||
"private": true,
|
||||
"description": "Unhosted maps app",
|
||||
"repository": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -42,8 +42,8 @@
|
||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||
|
||||
<script type="module" crossorigin src="/assets/main-CdHYOqFD.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CZWBbnnc.css">
|
||||
<script type="module" crossorigin src="/assets/main-B66TikLz.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-Bv3zmRKA.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="modal-portal"></div>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import Icon from 'marco/components/icon';
|
||||
|
||||
module('Integration | Component | icon', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders default 16px square dimensions', async function (assert) {
|
||||
await render(<template><Icon @name="zap" /></template>);
|
||||
|
||||
assert.dom('.icon').exists();
|
||||
assert.dom('.icon').hasAttribute('style', /width:16px;height:16px;/);
|
||||
});
|
||||
|
||||
test('it renders custom square size with @size', async function (assert) {
|
||||
await render(<template><Icon @name="zap" @size={{24}} /></template>);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:24px;height:24px;/);
|
||||
});
|
||||
|
||||
test('it supports custom @width and @height', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @width={{17}} @height={{22}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:17px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it allows @width to override while @height falls back to @size', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @width={{17}} @size={{22}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:17px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it allows @height to override while @width falls back to @size', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="bitcoin" @height={{22}} @size={{18}} /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:18px;height:22px;/);
|
||||
});
|
||||
|
||||
test('it supports explicit string units for width and height', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="zap" @width="100%" @height="2rem" /></template>
|
||||
);
|
||||
|
||||
assert.dom('.icon').hasAttribute('style', /width:100%;height:2rem;/);
|
||||
});
|
||||
|
||||
test('it splats HTML attributes to the icon element', async function (assert) {
|
||||
await render(
|
||||
<template><Icon @name="zap" data-test-custom-icon="true" /></template>
|
||||
);
|
||||
|
||||
assert.dom('[data-test-custom-icon="true"]').exists();
|
||||
});
|
||||
});
|
||||
@@ -411,4 +411,50 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
|
||||
assert.dom('button.btn-link').doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders payment methods when payment tags are present on place', async function (assert) {
|
||||
const place = {
|
||||
title: 'Coffee Place',
|
||||
lat: 52.52,
|
||||
lon: 13.4,
|
||||
osmTags: {
|
||||
'payment:cash': 'yes',
|
||||
'payment:cards': 'no',
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
},
|
||||
};
|
||||
|
||||
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||
|
||||
assert.dom('.place-payment-methods').exists();
|
||||
assert.dom('[data-test-payment-method="cash"]').exists();
|
||||
assert.dom('[data-test-payment-method="cards"]').hasClass('is-denied');
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert.dom('[data-test-payment-badge="lightning"]').exists();
|
||||
|
||||
const methodNames = Array.from(
|
||||
this.element.querySelectorAll('[data-test-payment-method]')
|
||||
).map((el) => el.getAttribute('data-test-payment-method'));
|
||||
assert.deepEqual(
|
||||
methodNames,
|
||||
['cash', 'cards', 'bitcoin'],
|
||||
'renders in stable order: cash, cards, bitcoin'
|
||||
);
|
||||
});
|
||||
|
||||
test('it does not render payment methods when no payment tags are present on place', async function (assert) {
|
||||
const place = {
|
||||
title: 'Simple Place',
|
||||
lat: 52.52,
|
||||
lon: 13.4,
|
||||
osmTags: {
|
||||
amenity: 'bench',
|
||||
},
|
||||
};
|
||||
|
||||
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||
|
||||
assert.dom('.place-payment-methods').doesNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import PlacePaymentMethods from 'marco/components/place-payment-methods';
|
||||
|
||||
module('Integration | Component | place-payment-methods', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders nothing when no payment tags are present', async function (assert) {
|
||||
await render(<template><PlacePaymentMethods @tags={{hash}} /></template>);
|
||||
assert.dom('.place-payment-methods').doesNotExist();
|
||||
|
||||
const otherTags = { amenity: 'restaurant', name: 'Bistro' };
|
||||
await render(
|
||||
<template><PlacePaymentMethods @tags={{otherTags}} /></template>
|
||||
);
|
||||
assert.dom('.place-payment-methods').doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders cash and card icons when accepted with aria-description and no title attribute', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'yes',
|
||||
'payment:cards': 'yes',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('.place-payment-methods').exists();
|
||||
assert.dom('[data-test-payment-method="cash"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.hasAttribute('aria-description', 'Cash accepted');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"] > .icon')
|
||||
.hasAttribute('style', /width:22px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-method="cards"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.hasAttribute('aria-description', 'Cards accepted');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"] > .icon')
|
||||
.hasAttribute('style', /width:22px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').doesNotExist();
|
||||
});
|
||||
|
||||
test('it shows strike-through when cards are explicitly denied', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'yes',
|
||||
'payment:cards': 'no',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('[data-test-payment-method="cards"]').exists();
|
||||
assert.dom('[data-test-payment-method="cards"]').hasClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cash"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
});
|
||||
|
||||
test('it shows strike-through when cash is explicitly denied', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'no',
|
||||
'payment:cards': 'yes',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('[data-test-payment-method="cash"]').exists();
|
||||
assert.dom('[data-test-payment-method="cash"]').hasClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="cards"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
});
|
||||
|
||||
test('it renders on-chain bitcoin without lightning zap badge', async function (assert) {
|
||||
const tags = {
|
||||
'currency:XBT': 'yes',
|
||||
'payment:onchain': 'yes',
|
||||
'payment:lightning': 'no',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert.dom('[data-test-payment-badge="lightning"]').doesNotExist();
|
||||
});
|
||||
|
||||
test('it renders bitcoin with lightning zap badge when lightning is accepted', async function (assert) {
|
||||
const tags = {
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.hasAttribute('aria-description', 'Bitcoin (Lightning) accepted');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"] > .icon')
|
||||
.hasAttribute('style', /width:17px;height:22px;color:currentColor/);
|
||||
assert.dom('[data-test-payment-badge="lightning"]').exists();
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasClass('icon-filled');
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasAttribute('style', /width:13px/);
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"] .icon')
|
||||
.hasAttribute('style', /color:#fff/);
|
||||
assert
|
||||
.dom('[data-test-payment-badge="lightning"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
});
|
||||
|
||||
test('it renders bitcoin with strike-through when explicitly denied', async function (assert) {
|
||||
const tags = {
|
||||
'currency:XBT': 'no',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').exists();
|
||||
assert.dom('[data-test-payment-method="bitcoin"]').hasClass('is-denied');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.doesNotHaveAttribute('title');
|
||||
assert
|
||||
.dom('[data-test-payment-method="bitcoin"]')
|
||||
.hasAttribute('aria-description', 'No Bitcoin');
|
||||
});
|
||||
|
||||
test('it preserves stable order (cash, cards, bitcoin) regardless of whether denied or accepted', async function (assert) {
|
||||
const tags = {
|
||||
'payment:cash': 'no',
|
||||
'payment:cards': 'yes',
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
};
|
||||
|
||||
await render(<template><PlacePaymentMethods @tags={{tags}} /></template>);
|
||||
|
||||
const methods = Array.from(
|
||||
this.element.querySelectorAll('[data-test-payment-method]')
|
||||
).map((el) => el.getAttribute('data-test-payment-method'));
|
||||
|
||||
assert.deepEqual(
|
||||
methods,
|
||||
['cash', 'cards', 'bitcoin'],
|
||||
'icons stay in fixed positions: cash, cards, bitcoin'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
import { parsePaymentMethods } from 'marco/utils/payment';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | payment', function () {
|
||||
test('it returns hasPaymentInfo: false for empty or non-payment tags', function (assert) {
|
||||
assert.deepEqual(parsePaymentMethods(undefined), {
|
||||
hasPaymentInfo: false,
|
||||
cash: null,
|
||||
cards: null,
|
||||
bitcoin: { status: null, lightning: false, onchain: false },
|
||||
});
|
||||
|
||||
assert.deepEqual(parsePaymentMethods({}), {
|
||||
hasPaymentInfo: false,
|
||||
cash: null,
|
||||
cards: null,
|
||||
bitcoin: { status: null, lightning: false, onchain: false },
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
parsePaymentMethods({ amenity: 'cafe', name: 'Coffee Shop' }),
|
||||
{
|
||||
hasPaymentInfo: false,
|
||||
cash: null,
|
||||
cards: null,
|
||||
bitcoin: { status: null, lightning: false, onchain: false },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
module('cash', function () {
|
||||
test('it identifies cash as accepted', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cash': 'yes' }).cash,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cash': 'only' }).cash,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:coins': 'yes' }).cash,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:notes': 'yes' }).cash,
|
||||
'accepted'
|
||||
);
|
||||
});
|
||||
|
||||
test('it identifies cash as denied', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cash': 'no' }).cash,
|
||||
'denied'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:coins': 'no', 'payment:notes': 'no' })
|
||||
.cash,
|
||||
'denied'
|
||||
);
|
||||
});
|
||||
|
||||
test('it keeps cash as null when cash tags are absent', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cards': 'yes' }).cash,
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
module('cards', function () {
|
||||
test('it identifies cards as accepted from generic or card tags', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cards': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:credit_cards': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:debit_cards': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:contactless': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:visa': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:mastercard': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:american_express': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:girocard': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:apple_pay': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:google_pay': 'yes' }).cards,
|
||||
'accepted'
|
||||
);
|
||||
});
|
||||
|
||||
test('it identifies cards as denied when explicitly disabled', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cards': 'no' }).cards,
|
||||
'denied'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({
|
||||
'payment:credit_cards': 'no',
|
||||
'payment:debit_cards': 'no',
|
||||
}).cards,
|
||||
'denied'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:cash': 'only' }).cards,
|
||||
'denied'
|
||||
);
|
||||
});
|
||||
|
||||
test('it accepts cards if debit is accepted even if credit_cards is no', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({
|
||||
'payment:credit_cards': 'no',
|
||||
'payment:debit_cards': 'yes',
|
||||
}).cards,
|
||||
'accepted'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
module('bitcoin', function () {
|
||||
test('it identifies on-chain bitcoin acceptance', function (assert) {
|
||||
const result = parsePaymentMethods({ 'currency:XBT': 'yes' });
|
||||
assert.strictEqual(result.bitcoin.status, 'accepted');
|
||||
assert.true(result.bitcoin.onchain);
|
||||
assert.false(result.bitcoin.lightning);
|
||||
});
|
||||
|
||||
test('it identifies bitcoin from payment:onchain tag', function (assert) {
|
||||
const result = parsePaymentMethods({ 'payment:onchain': 'yes' });
|
||||
assert.strictEqual(result.bitcoin.status, 'accepted');
|
||||
assert.true(result.bitcoin.onchain);
|
||||
assert.false(result.bitcoin.lightning);
|
||||
});
|
||||
|
||||
test('it identifies bitcoin with lightning', function (assert) {
|
||||
const result = parsePaymentMethods({
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
'payment:onchain': 'no',
|
||||
});
|
||||
assert.strictEqual(result.bitcoin.status, 'accepted');
|
||||
assert.true(result.bitcoin.lightning);
|
||||
assert.false(result.bitcoin.onchain);
|
||||
});
|
||||
|
||||
test('it identifies bitcoin with both lightning and on-chain', function (assert) {
|
||||
const result = parsePaymentMethods({
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
'payment:onchain': 'yes',
|
||||
});
|
||||
assert.strictEqual(result.bitcoin.status, 'accepted');
|
||||
assert.true(result.bitcoin.lightning);
|
||||
assert.true(result.bitcoin.onchain);
|
||||
});
|
||||
|
||||
test('it supports fallback bitcoin tags', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:bitcoin': 'yes' }).bitcoin.status,
|
||||
'accepted'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'currency:BTC': 'yes' }).bitcoin.status,
|
||||
'accepted'
|
||||
);
|
||||
});
|
||||
|
||||
test('it identifies bitcoin as denied when explicitly no', function (assert) {
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'currency:XBT': 'no' }).bitcoin.status,
|
||||
'denied'
|
||||
);
|
||||
assert.strictEqual(
|
||||
parsePaymentMethods({ 'payment:bitcoin': 'no' }).bitcoin.status,
|
||||
'denied'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('it handles mixed tags and sets hasPaymentInfo to true', function (assert) {
|
||||
const result = parsePaymentMethods({
|
||||
'payment:cash': 'yes',
|
||||
'payment:cards': 'no',
|
||||
'currency:XBT': 'yes',
|
||||
'payment:lightning': 'yes',
|
||||
});
|
||||
|
||||
assert.true(result.hasPaymentInfo);
|
||||
assert.strictEqual(result.cash, 'accepted');
|
||||
assert.strictEqual(result.cards, 'denied');
|
||||
assert.strictEqual(result.bitcoin.status, 'accepted');
|
||||
assert.true(result.bitcoin.lightning);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user