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 NOT the same numbers as the previous version of this page
The component points at the dev gateway, which fronts the unreleased BPF develop build. That build adds surcharges to cabin prices, so every value here is higher than it used to be and carries decimals — Helsinki to Tallinn is 35.90 € here against 27,00 € on the old page. Do not compare the two versions number for number, and do not quote these amounts anywhere.
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. One voyage type is missing on purpose: cruise_multiship is in the API enum but answers HTTP 500 on every route tried, so it cannot be used on a page. Card 10.1 shows what that looks like.
1.1 Shuttle
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 35.90 € at /en/, 16,30 € at /fi/, 173 kr at /sv/. The reference for most cards below.
1.2 Cruise
portFrom=”tal” portTo=”sto” voyageType=”cruise”
Expected: 208.60 € at /en/, 138,60 € at /fi/, 1 474 kr at /sv/. Prices on every market in both directions.
1.3 Megastar cruise
portFrom=”tal” portTo=”hel” voyageType=”cruise_megastar”
Expected: 36.40 € at /en/. Tallinn to Helsinki is the only port pair that returns a Megastar price, so do not change these ports.
1.4 Day cruise
portFrom=”hel” portTo=”tal” voyageType=”day_cruise”
Expected: 31.40 € at /en/. Only three port pairs return a day cruise price — Helsinki and Tallinn both ways, and Mariehamn to Turku.
1.5 Route trip
portFrom=”tal” portTo=”sto” voyageType=”routetrip”
Expected: 133.80 € at /en/. routetrip and shuttle are the same code path in the API — both dispatch to the same handler and voyageType is never read again — so those two can never differ from each other.
2. clubOneLevel — membership pricing
All cards use the same Helsinki to Tallinn shuttle so the levels are directly comparable. On production the tiers step down correctly. On dev they do not: silver, gold and platinum all return the same price, because the non-production Club One discount data is wrong. That is a data problem in the upstream pricing system, not the component — so tier behaviour cannot be validated on this page.
2.1 clubOneLevel omitted
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 35.90 € 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: 35.90 € 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: 32.90 € — three euros below 2.2.
2.4 Silver
clubOneLevel=”silver”
Expected: 30.90 €. On dev this matches gold and platinum, which is the wrong tier data described above, not the component picking the wrong level.
2.5 Gold
clubOneLevel=”gold”
Expected: 30.90 €, level with 2.4 and 2.6 on dev.
2.6 Platinum
clubOneLevel=”platinum”
Expected: 30.90 €. The highest tier should be the cheapest of the four; on dev it is not distinguishable from silver.
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, not everything after it — card 3.5 is identical to writing dateTo=”2026-09-08″. 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 thing to know before publishing a from price: on dev a WIDER window can come back MORE expensive. Compare 3.2 with 3.3 — the next 30 days contains the next 7 days, yet it answers 40.30 € against 35.90 €, so a cheaper sailing sat inside the wider search and was not the answer it gave. The departureDates list in the network tab shows why: it holds the dates the returned price is available on, and the 30-day answer does not start until September. This is a known defect in the unreleased API build, already filed, and there is no need to report it again.
3.1 Default window
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 35.90 €, 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: 35.90 €, 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: 40.30 € — DEARER than 3.2 even though this window contains it. Read this card next to 3.2: it is the clearest proof of the wider-window defect described above, and the reason a longer window is not a safer way to find a low price.
3.4 Fixed window
dateFrom=”2026-09-01″ dateTo=”2026-09-30″
Expected: 40.30 €. A window entirely in the past returns an empty result and therefore spins, so update these dates once they pass — a fixed window is a maintenance commitment. Card 10.6 shows the symptom.
3.5 Start date only
dateFrom=”2026-09-01″
Expected: 40.30 €. dateTo may be left off, but this is NOT open-ended: it searches seven days from dateFrom, so this card is identical to adding dateTo=”2026-09-08″. Write both dates if you mean a longer window.
3.6 End date only
dateTo=”2026-09-30″
Expected: 40.30 €. Dropping dateFrom instead searches from TODAY to the date given, so this covers a much wider span than 3.5 — 27 sailing dates against 8 — 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-09-01″ dateTo=”2026-09-30″
Expected: 35.90 € — 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 — add the vehicle price
This attribute is a switch, not a value: writing it at all asks for the vehicle price, 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. The price shown is the cabin and the vehicle added together, not the vehicle alone.
4.1 Attribute omitted
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 35.90 €, the travel class price on its own. Baseline for 4.2.
4.2 Cabin plus vehicle
vehiclePriceIncluded (bare, no value)
Expected: 66.90 €, which is 4.1 plus the vehicle. Note the cabin half of that sum is not always the same number as 4.1 — on some routes the API returns a different cabin price once a vehicle is requested.
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.
5.1 No filter
portFrom=”hel” portTo=”tal” voyageType=”shuttle”
Expected: 35.90 €, the cheapest across all classes.
5.2 Class A
travelClassCodeFrom=”a”
Expected: 95.90 €. A higher number than 5.1 is correct — A is a premium class.
5.3 Class B
travelClassCodeFrom=”b”
Expected: 72.30 € — the same as class E on this route, not a distinct step.
5.4 Deluxe sea view
travelClassCodeFrom=”luxs”
Expected: 275.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: 108.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: 35.90 € 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: 208.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: 44.80 € at /en/ and /de/, 25,60 € at /fi/, /et/, /lv/ and /ru/, and 271 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 unreleased build removes RU from its market enum, which would return HTTP 400 and blank every Russian page, so the locale was remapped. 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
Restricts the search to one ship — but only on some routes. On shuttle and day_cruise it works as you would expect: the ship must actually serve the route, and naming one that does not gives no price. On cruise routes it is accepted and then ignored, so every ship returns the unfiltered price. Tallinn to Stockholm answers 208.60 € for all 14 codes, including megastar, which cannot sail that route at all. Card 9.4 is that case. Do not use shipCode to advertise a specific ship on a cruise.
9.1 Megastar on its own route
portFrom=”hel” portTo=”tal” shipCode=”megastar” voyageType=”shuttle”
Expected: 35.90 €, level with card 1.1. Megastar sails Helsinki to Tallinn and happens to carry the cheapest fare, so filtering to it changes nothing here.
9.2 MyStar, same route
portFrom=”hel” portTo=”tal” shipCode=”mystar” voyageType=”shuttle”
Expected: 35.90 €, the same as 9.1 — two ships can legitimately share a fare.
9.3 Victoria I, same route
portFrom=”hel” portTo=”tal” shipCode=”victoria” voyageType=”shuttle”
Expected: 42.30 € — DIFFERENT from 9.1 and 9.2. This single number is the proof that the filter is actually applied rather than accepted and ignored.
9.4 Single-ship route
portFrom=”tal” portTo=”sto” shipCode=”queen” voyageType=”cruise”
Expected: 208.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. The value is in the API enum, so it looks valid, but it answers HTTP 500 on every route tried. It is the one voyage type from section 1 you cannot use on a page.
10.2 A tier that does not exist
clubOneLevel=”diamond”
Expect nothing. The API rejects the value with HTTP 400. The four 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 real cabin or ship that does not serve the route, a window that has already passed, 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 real ship on the wrong route
portFrom=”hel” portTo=”tal” shipCode=”queen”
Expect a spinner forever. Baltic Queen is a real ship and the code is accepted, but she sails Tallinn to Stockholm, so there is no matching sailing here. This is a shuttle route, which is where the filter is actually applied — see the section 9 note for the cruise routes where it is not.
10.6 A window that has already passed
dateFrom=”2020-01-01″ dateTo=”2020-02-01″
Expect a spinner forever. This is what a fixed window from section 3 turns into once its dates go by, which is why a rolling daysFromNow needs no upkeep and a fixed window does.
C. A price appears, and it is the wrong one
The most expensive group to get wrong, because there is no symptom at all. Each card below publishes a believable number that is not the number intended, and nothing on the page or in the console reports it. The only defence is to compare against the same card without the attribute you are unsure about.
10.7 Trying to switch the vehicle off
vehiclePriceIncluded=”false”
Expect 66.90 €, NOT the 35.90 € of card 4.1. Writing the attribute at all switches the vehicle price on, whatever follows the equals sign — the word false included. Delete the attribute to turn it off.
10.8 A mistyped campaign code
voucherId=”NOTAREALCODE”
Expect 35.90 €, 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 44.80 € where card 1.1 gives 35.90 € for a shuttle on the same ports. Both are real products and both look right, so a voyage type picked in error is invisible. Confirm which product the page is selling.