BestPrice — component test page
Every correct configuration of the BestPrice component, rendered side by side. Each card lists the attributes it uses and the price it should produce.
These are live production prices
The component reads the production pricing API (0.1.35). Every expected figure on this page was measured on 14 September 2026 and moves with sailing inventory, so treat the numbers as reference points and the described behaviour as the durable part. Since 0.1.35 a request with a vehicle returns one combined price that is bookable on a single departure — there is no separate car figure anymore.
How to read this page
Every card in sections 1 to 9 is a correct usage, so all of them should show a price. A card can do two other things and the difference between them is the whole point. A spinner that never stops means the request was fine but no price came back — a date window that has already passed, a route with no sailings left, or the API being down. Nothing at all means the request was rejected, so on a page of yours it means an attribute is wrong. Section 10 shows both symptoms on purpose. The console names the reason either way, so keep it open.
All values are written in lowercase, which is the CMS convention. The component trims and uppercases every enum before it reaches the API, so authoring hel, shuttle or gold is correct and you never need to match the API’s own casing. The one exception is voucherId — see section 6.
Market comes from the locale, not an attribute, so reload this same page at /sv/, /fi/, /et/, /lv/, /de/, /ru/ and with no prefix for English.
1. voyageType — baseline, required attributes only
Only portFrom, portTo and voyageType. The fetch is gated on all three, so nothing is requested until every one is present. Route availability also depends on the market, which comes from the page language — these cards all depart from Helsinki or Tallinn so they price on every locale. Two values from the old API enum are gone: cruise_megastar and cruise_multiship are both rejected with HTTP 400 since 0.1.35 and cannot be used on a page. Card 10.1 shows what that looks like.
1.1 Shuttle
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 € at /en/, 16,30 € at /fi/, 180 kr at /sv/. The reference for most cards below.
1.2 Cruise
portFrom=”tal” portTo=”sto” voyageType=”cruise”
Expected: 178.60 € at /en/, 98,60 € at /fi/, 1 099 kr at /sv/. Prices on every market in both directions.
1.3 Shopping cruise
portFrom=”hel” portTo=”tal” voyageType=”day_cruise_shopping”
Expected: 28.80 € at /en/ and /fi/, 324 kr at /sv/. This voyage type replaced the retired cruise_megastar on this page — it prices on Helsinki–Tallinn both ways.
1.4 Day cruise
portFrom=”hel” portTo=”tal” voyageType=”day_cruise”
Expected: 31.80 € at /en/. Only three port pairs return a day cruise price — Helsinki and Tallinn both ways, and Mariehamn to Turku — and departures are sparse, so the dates list can hold a single day.
1.5 Route trip
portFrom=”tal” portTo=”sto” voyageType=”routetrip”
Expected: 127.80 € at /en/. routetrip and shuttle are the same code path in the API, so those two can never differ from each other — re-verified on 0.1.35 on both this route and Helsinki–Tallinn.
2. clubOneLevel — membership pricing
All cards use the same Helsinki to Tallinn shuttle so the levels are directly comparable. The tiers step down correctly on production: on the English page the ladder runs 40.30 / 39.30 / 38.30 / 36.30 / 35.30 € from non-member to platinum. The absolute discount per tier is set by the pricing system and moves with inventory; the ordering is the durable part.
2.1 clubOneLevel omitted
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 € when logged out, since no session resolves to the non-member price. Logged in it takes your own tier. No reload is needed — the level re-resolves on sign-in and sign-out. This is the usual way to author a page: omit the attribute and let the visitor’s own tier apply.
2.2 Non-member
clubOneLevel=”none”
Expected: 40.30 € at /en/. The public fare and the baseline for the tiers below. Pins the price to the non-member fare even for a signed-in member.
2.3 Club
clubOneLevel=”club”
Expected: 39.30 € — one euro below 2.2.
2.4 Silver
clubOneLevel=”silver”
Expected: 38.30 €, between club and gold.
2.5 Gold
clubOneLevel=”gold”
Expected: 36.30 €, below silver and above platinum.
2.6 Platinum
clubOneLevel=”platinum”
Expected: 35.30 €. The highest tier is the cheapest of the four member levels.
3. Date filtering
Two mutually exclusive modes. daysFromNow is a rolling window from today; dateFrom plus dateTo is a fixed window. When daysFromNow is present the fixed dates are not sent at all. Omitting all three behaves exactly like daysFromNow=”7″.
Leaving one of the two dates off does NOT mean open-ended, and the two halves do not behave alike. dateFrom on its own searches seven days from that date — card 3.5 is identical to writing dateTo seven days later. dateTo on its own searches from today up to that date, which is card 3.6. Only a dateFrom and dateTo pair gives you exactly the window you wrote.
One trap to know before publishing a fixed window: a window in the RECENT past can still return a price — for departures that already sailed. dateFrom=”2026-09-01″ tested on 14 September answered 40.30 € with departure dates of 7 and 8 September, all in the past. Only a long-gone window comes back empty and spins (card 10.6). So stale fixed dates do not just stop working, they can silently show a price nobody can book — one more reason to prefer a rolling daysFromNow.
3.1 Default window
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 €, identical to 3.2. The API fallback window is seven days, so omitting the dates is not the same as searching all dates — worth knowing before publishing a from price.
3.2 Next 7 days
daysFromNow=”7″
Expected: 40.30 €, the same as 3.1. A rolling window needs no maintenance, which makes it the safest choice for a page that stays up.
3.3 Next 30 days
daysFromNow=”30″
Expected: 33.90 € — cheaper than 3.2, as a wider search should be. The dev-era defect where a wider window answered a higher price is fixed in 0.1.35; if these two cards ever invert again, report it.
3.4 Fixed window
dateFrom=”2026-10-01″ dateTo=”2026-10-31″
Expected: 33.90 €. A fixed window is a maintenance commitment: once the dates pass it first shows stale prices for departed sailings (see the section note) and eventually spins. Update these dates when October ends — card 10.6 shows the end state.
3.5 Start date only
dateFrom=”2026-10-01″
Expected: 33.90 €. dateTo may be left off, but this is NOT open-ended: it searches seven days from dateFrom — the network tab shows departure dates 1 to 8 October only. Write both dates if you mean a longer window.
3.6 End date only
dateTo=”2026-10-31″
Expected: 33.90 €. Dropping dateFrom instead searches from TODAY to the date given, so this covers a much wider span than 3.5 — 47 sailing dates against 8 when measured — even though both land on the same price. The two halves are not symmetrical, so check which one you actually left off.
3.7 daysFromNow wins
daysFromNow=”7″ dateFrom=”2026-10-01″ dateTo=”2026-10-31″
Expected: 40.30 € — identical to 3.2 rather than 3.4, which proves the fixed dates were discarded. Check the network tab: the request contains daysFromNow and does NOT contain dateFrom or dateTo. Do not author both modes at once; this card exists only to document which one wins.
4. vehiclePriceIncluded — one price with the vehicle
This attribute is a switch, not a value: writing it at all asks for the vehicle, and removing it is the only way to turn it off. Whatever you type after an equals sign is ignored, including the word false, so write it bare as in 4.2. Since 0.1.35 the API answers with ONE combined price that covers the travel class and the car together and is bookable on a single departure — there is no separate car figure, and the dates list shrinks to the departures where that combination genuinely exists.
4.1 Attribute omitted
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 €, the travel class price on its own. Baseline for 4.2.
4.2 One price with the vehicle
vehiclePriceIncluded (bare, no value)
Expected: 57.90 €. Do not expect 4.1 plus a car figure: the cheapest seat is often a deck seat that cannot take a car at all, so the combined price starts from the cheapest class that can. Fewer departure dates than 4.1 is normal — only genuinely bookable combinations are offered.
5. travelClassCodeFrom — travel class filter
Restricts the search to one cabin category. The codes that work are route-dependent: on this route 15 of the 28 tested price — deck, dsc, dcc, dfc, dpc, e, b, a, apre, ac, luxs, lux, suit, fc and fdl — while c and c2 price on Stockholm to Helsinki but return nothing here. Filtering usually makes the price HIGHER, because the unfiltered card already reports the cheapest class available. Avoid deck: it is the lowest class, so filtering to it answers exactly the unfiltered price and pins nothing.
5.1 No filter
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 €, the cheapest across all classes.
5.2 Class A
travelClassCodeFrom=”a”
Expected: 93.90 €. A higher number than 5.1 is correct — A is a premium class.
5.3 Class B
travelClassCodeFrom=”b”
Expected: 70.30 € — between the unfiltered price and class A.
5.4 Deluxe sea view
travelClassCodeFrom=”luxs”
Expected: 255.30 €. Far more classes work than the three this page once listed — check the set above before assuming a code is unsupported.
6. voucherId — discount codes
The one attribute with no live card on this page, because a made-up voucher id demonstrates nothing.
How to author voucherId
This is the ONE value that is not converted to uppercase. Type it exactly as it was issued.
Every other value on this page can be written in any casing, because the component uppercases it before the request. Campaign codes are not an enum — they are opaque strings that may be case-sensitive — so changing their casing could turn a working voucher into an ignored one.
An unrecognised voucher is ignored silently and the normal price comes back, so a mistyped code cannot be spotted from the page: it looks exactly like a campaign that offers no discount on this route. Copy the id from the campaign rather than retyping it, and compare the price against the same card without the attribute to confirm the discount actually applied.
7. Combined attributes and inline placement
The component renders a bare text string with no wrapper element, so it inherits the typography of whatever contains it — including the spinner, which is sized to inherit.
7.1 All filters at once
clubOneLevel=”gold” daysFromNow=”14″ portFrom=”hel” portTo=”tal” travelClassCodeFrom=”a” voyageType=”cruise”
Expected: 113.60 €. Every filter applies at the same time and they narrow the search together.
7.2 Inside a large heading
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 40.30 € rendered at heading size, and while loading the spinner is heading-sized too rather than a fixed small icon.
from
7.3 Mid-sentence
portFrom=”tal” portTo=”sto” voyageType=”cruise”
Expected: 178.60 € sitting inline in running copy at body size, with the surrounding words spaced normally.
Tallinn to Stockholm cruises start from per person, one way.
8. Market and currency formatting
There is no market attribute. Market is derived from the page locale, so this group is tested by reloading the same page under each locale prefix.
8.1 Same card, every locale
portFrom=”hel” portTo=”tal” voyageType=”cruise”
Expected: 28.80 € at /en/ and /de/, 25,60 € at /fi/, /et/, /lv/ and /ru/, and 283 kr at /sv/. The price re-fetches on locale change, it is not just reformatted.
Locale checklist
/sv/ to market SE — the important one: kr with ZERO decimals.
/fi/ to FI, /et/ to EE, /lv/ to LV, /de/ to DE, no prefix to XZ. All euro with two decimals.
/ru/ maps to EE, not RU. The pricing API has no RU market anymore — sending it returns HTTP 400 — so the component remaps the locale. RU and EE price identically, so nothing changed visibly.
/ja/ and /zh/ both fall back to XZ. There is no market mapping for either locale.
One known formatting issue remains: the formatter is handed the raw CMS locale, and several of those are not the language they look like — /dk-da/ and /dk-en/ resolve to en-US and /ee-*/ to Ewe, so the same krona price renders as 1 154,60 on /sv/, 1.154,60 on /da/ and 1,154.60 on /dk-da/.
9. shipCode — restrict to one ship
Only day_cruise actually applies this filter. On shuttle, routetrip, cruise and day_cruise_shopping the attribute is accepted and then ignored, so every ship — including one that cannot sail the route at all — returns the unfiltered cheapest price while the page copy names a specific vessel. Do not use shipCode to advertise a ship anywhere except a day cruise, and even there check the card, because a ship that does not run day cruises returns no price at all and the card spins (card 10.5).
9.1 Megastar on a shuttle
portFrom=”hel” portTo=”tal” shipCode=”megastar” voyageType=”shuttle”
Expected: 40.30 €, level with card 1.1 — but not because Megastar is cheapest. On shuttles the filter is ignored, so all 14 ship codes return this same number.
9.2 Victoria I, same shuttle
portFrom=”hel” portTo=”tal” shipCode=”victoria” voyageType=”shuttle”
Expected: 40.30 €, identical to 9.1. On the old API build Victoria answered a different fare here, which proved the shuttle filter worked; since 0.1.35 it does not — this card documents the change.
9.3 MyStar on a day cruise
portFrom=”hel” portTo=”tal” shipCode=”mystar” voyageType=”day_cruise”
Expected: 32.80 € against 31.80 € on the unfiltered card 1.4 — a different number, which is the proof the day cruise filter is genuinely applied. The filtered price is not always higher, so read it as ”different”, not ”more”.
9.4 Single-ship route
portFrom=”tal” portTo=”sto” shipCode=”queen” voyageType=”cruise”
Expected: 178.60 €, matching card 1.2 — but NOT because Baltic Queen is the cheapest ship here. On cruise routes the filter is ignored, so this is simply the unfiltered price. Swap queen for megastar and the number does not move, which is the proof.
10. Incorrect usage — do not copy from this section
Every card below is wrong on purpose
These exist so you can recognise a symptom on your own page and know which of the three things went wrong. Copy from sections 1 to 9 only. Each card names the mistake, so if your page looks like one of these, read across to find out why.
A. Nothing renders at all
The component asked for a price and was told the request made no sense, so it shows nothing rather than a spinner. An empty space where a price should be is the signal that an attribute is wrong — start with the three required ones, then anything you typed by hand. A request that fails outright — the API down, or the gateway refusing it — blanks for the same reason, so a blank card is not always your fault. The console says which it was.
10.1 A voyage type that cannot price
voyageType=”cruise_multiship”
Expect nothing. Since 0.1.35 the API’s enum is only the five values in section 1: cruise_multiship and cruise_megastar are both rejected with HTTP 400, so a page still authoring either shows a blank where the price was.
10.2 A tier that does not exist
clubOneLevel=”diamond”
Expect nothing. The API rejects the value with HTTP 400. The real levels are none, club, silver, gold and platinum — see section 2.
10.3 A date in the wrong format
dateFrom=”01-09-2026″
Expect nothing. Dates must be written year first, as 2026-09-01. This one is caught before any request is sent, so unlike 10.1 and 10.2 the console stays quiet — a blank card with a silent console means a date.
B. The spinner never stops
These are the mistakes the component cannot catch. The API answers each of them exactly as it answers a genuinely sold-out route — a normal, successful, empty response — so a typo here is indistinguishable from no availability. An unknown cabin code, a ship that does not run day cruises on a day_cruise card, a long-gone date window, a dateFrom later than the dateTo and a port pair with no service between them all land here. If a spinner never resolves, check these before concluding there are no sailings.
10.4 A cabin code that is not a code
travelClassCodeFrom=”deluxe”
Expect a spinner forever. Cabin codes are short — luxs, not deluxe. Section 5 lists the 15 that work on this route.
10.5 A ship that does not run day cruises
portFrom=”hel” portTo=”tal” shipCode=”victoria” voyageType=”day_cruise”
Expect a spinner forever. Victoria I is a real ship on this route, but she runs no day cruises, and day_cruise is the one voyage type where the ship filter is applied — so no sailing matches. On any other voyage type this mistake does not spin, it silently shows the wrong price: see card 10.7.
10.6 A window that has long passed
dateFrom=”2020-01-01″ dateTo=”2020-02-01″
Expect a spinner forever. This is what a fixed window from section 3 eventually turns into — but note a RECENTLY passed window is worse: it can still show a price for departures that already sailed (see the section 3 note). Both are the reason a rolling daysFromNow needs no upkeep and a fixed window does.
C. A price appears, and it is the wrong one
The worst class of mistake, because nothing looks broken. The card shows a real, live price — just not the price the page copy promises.
10.7 A named ship the route ignores
portFrom=”hel” portTo=”tal” shipCode=”queen” voyageType=”shuttle”
Expect 40.30 € — the plain unfiltered shuttle price. Baltic Queen does not sail this route at all, but on a shuttle the ship filter is ignored, so the card happily shows the fleet-wide cheapest fare while your copy names a ship it may not be valid on. Only day_cruise honours shipCode — see section 9.
10.8 A mistyped campaign code
voucherId=”NOTAREALCODE”
Expect 40.30 €, exactly the same as card 4.1 with no voucher at all. The response is identical either way, so a typo in a campaign code silently publishes the full fare. Always check the discount landed.
10.9 The wrong voyage type
portFrom=”hel” portTo=”tal” voyageType=”cruise”
Expect 28.80 € where card 1.1 gives 40.30 € for a shuttle on the same ports. Both are real products and both look right — the cruise is currently the CHEAPER of the two, so nothing flags the mix-up. Confirm which product the page is selling.