258 lines
11 KiB
Swift
258 lines
11 KiB
Swift
import Elementary
|
||
import Foundation
|
||
import Persistence
|
||
import Testing
|
||
|
||
@testable import WebsiteLibrary
|
||
|
||
@Suite(
|
||
"Trade pages",
|
||
.tags(.page)
|
||
)
|
||
struct TradePagesTests {
|
||
|
||
// MARK: OfferPage tests
|
||
|
||
@Test
|
||
func `offer sheet fills in a tenth under asking and offers the picks the seller would hear`() {
|
||
let html = OfferPage(listing: .stub(), mode: .offer).render()
|
||
|
||
#expect(html.contains(#"action="/listing/nightward-1/offer" method="post""#))
|
||
#expect(html.contains(#"name="amount" value="216""#))
|
||
#expect(html.contains(#"href="/listing/nightward-1/offer?amount=216" aria-current="true">€216 · −10%"#))
|
||
#expect(html.contains("€192 · −20%"))
|
||
#expect(html.contains("auto-declines under <strong>€192</strong>"))
|
||
#expect(html.contains("+ €6 shipping to NL"))
|
||
#expect(html.contains(#"content="noindex""#))
|
||
}
|
||
|
||
@Test
|
||
func `offer sheet leaves out a pick under the seller's floor`() {
|
||
// €88 asking: the floor rounds up to €71, over the −20% pick of €70.
|
||
let html = OfferPage(listing: .stub(priceCents: 8_800), mode: .offer).render()
|
||
|
||
#expect(html.contains("€75 · −15%"))
|
||
#expect(!html.contains("−20%"))
|
||
}
|
||
|
||
@Test
|
||
func `offer sheet gives back what was typed, saying what went wrong`() {
|
||
let html = OfferPage(
|
||
listing: .stub(),
|
||
mode: .offer,
|
||
amountCents: 15_000,
|
||
note: "Bundle?",
|
||
error: TradeError.offerTooLow(floorCents: 19_200).explanation
|
||
).render()
|
||
|
||
#expect(html.contains(#"value="150""#))
|
||
#expect(html.contains(">Bundle?</textarea>"))
|
||
#expect(html.contains(#"role="alert">The seller auto-declines offers under €192."#))
|
||
}
|
||
|
||
@Test
|
||
func `buy sheet sums the asking price and the shipping`() {
|
||
let html = OfferPage(listing: .stub(), mode: .buy).render()
|
||
|
||
#expect(html.contains(#"action="/listing/nightward-1/buy" method="post""#))
|
||
#expect(html.contains("Agree to buy for €246"))
|
||
#expect(html.contains("CCN handles no money"))
|
||
#expect(!html.contains(#"name="amount""#))
|
||
}
|
||
|
||
// MARK: SalePage tests
|
||
|
||
@Test
|
||
func `sale record hands each party the step that is theirs`() {
|
||
func page(_ sale: Sale, _ role: Sale.Role) -> String {
|
||
SalePage(sale: sale, role: role, now: .stub).render()
|
||
}
|
||
|
||
#expect(page(.stub(), .buyer).contains(#"name="step" value="paid""#))
|
||
#expect(page(.stub(), .seller).contains("Waiting for the buyer to pay"))
|
||
#expect(page(.stub(paid: true), .seller).contains(#"name="step" value="sent""#))
|
||
#expect(page(.stub(paid: true), .buyer).contains("Waiting for the seller to dispatch"))
|
||
#expect(page(.stub(paid: true, sent: true), .buyer).contains(#"name="step" value="received""#))
|
||
#expect(page(.stub(paid: true, sent: true, received: true), .seller).contains(#"name="stars" value="5""#))
|
||
#expect(page(.stub(paid: true, sent: true, received: true, buyerRating: 5), .buyer).contains("Waiting for the other side"))
|
||
#expect(!page(.stub(paid: true, sent: true, received: true, buyerRating: 5, sellerRating: 4), .buyer).contains(#"name="step""#))
|
||
}
|
||
|
||
@Test
|
||
func `sale record states what was agreed, frozen, and where the handshake stands`() {
|
||
let html = SalePage(sale: .stub(paid: true), role: .buyer, now: .stub).render()
|
||
|
||
#expect(html.contains("Sale record · CCN-48213 · you are the buyer"))
|
||
#expect(html.contains("Agreed today, 09:14 with Kestrel Row Comics · €215 + €6 tracked shipping"))
|
||
#expect(html.contains("Awaiting dispatch"))
|
||
#expect(html.contains("CGC 9.8 · cert 4312087-002"))
|
||
#expect(html.contains("€215 · offer accepted (asked €240)"))
|
||
#expect(html.contains("marked this as paid by iDEAL today, 09:20"))
|
||
#expect(html.contains("stepper__step--done"))
|
||
#expect(html.components(separatedBy: "stepper__step--current").count - 1 == 1)
|
||
#expect(html.contains(#"href="/messages/00000000-0000-0000-0000-0000000000cc""#))
|
||
}
|
||
|
||
@Test
|
||
func `sale record shows the seller their buyer, and both the ratings once given`() {
|
||
let html = SalePage(
|
||
sale: .stub(priceCents: 24_000, paid: true, sent: true, received: true, buyerRating: 5, sellerRating: 4),
|
||
role: .seller,
|
||
now: .stub
|
||
).render()
|
||
|
||
#expect(html.contains("you are the seller"))
|
||
#expect(html.contains("Mara Visser"))
|
||
#expect(html.contains("bought at the asking price"))
|
||
#expect(html.contains("Complete"))
|
||
#expect(html.contains("★★★★★"))
|
||
#expect(html.contains("★★★★☆"))
|
||
#expect(!html.contains("Visit shop"))
|
||
}
|
||
|
||
// MARK: MessagesPage tests
|
||
|
||
@Test
|
||
func `messages tell the viewer's words from the other side's`() {
|
||
let html = MessagesPage(
|
||
conversations: [],
|
||
open: .stub(entries: [
|
||
.text("Is the case clean?", from: .mara),
|
||
.text("Clean all round.", from: .joost),
|
||
]),
|
||
now: .stub
|
||
).render()
|
||
|
||
#expect(html.contains(#"entry--text entry--own"><p class="entry__bubble">Is the case clean?"#))
|
||
#expect(html.contains(#"entry--text entry--theirs"><p class="entry__bubble">Clean all round."#))
|
||
#expect(html.contains("You · today, 09:14"))
|
||
#expect(html.contains("Kestrel Row Comics · today, 09:14"))
|
||
#expect(html.contains(#"action="/messages/00000000-0000-0000-0000-0000000000cc" method="post""#))
|
||
}
|
||
|
||
@Test
|
||
func `messages let the seller alone answer an open offer`() {
|
||
let entries = [Conversation.Entry(kind: .offer, sender: .mara, body: "Would you take €215?", offer: .stub(), createdAt: .stub)]
|
||
let selling = MessagesPage(conversations: [], open: .stub(side: .selling, entries: entries), now: .stub).render()
|
||
let buying = MessagesPage(conversations: [], open: .stub(side: .buying, entries: entries), now: .stub).render()
|
||
|
||
#expect(selling.contains("Their offer"))
|
||
#expect(selling.contains(#"action="/sell/offers/00000000-0000-0000-0000-0000000000aa""#))
|
||
#expect(selling.contains("47 h left"))
|
||
#expect(selling.contains("“Would you take €215?”"))
|
||
#expect(buying.contains("Your offer"))
|
||
#expect(buying.contains("Offer open · €215"))
|
||
#expect(!buying.contains("/sell/offers/"))
|
||
// One open offer per listing: no way to a second while it waits.
|
||
#expect(!buying.contains(#"href="/listing/nightward-1/offer""#))
|
||
}
|
||
|
||
@Test
|
||
func `messages lead to the sale record from the event that opened it, once`() {
|
||
let html = MessagesPage(
|
||
conversations: [],
|
||
open: .stub(
|
||
entries: [
|
||
.init(kind: .offer, sender: .mara, body: "", offer: .stub(status: .accepted), createdAt: .stub),
|
||
.init(kind: .event, sender: .joost, body: "accepted the offer", saleNumber: 48_213, createdAt: .stub),
|
||
.init(kind: .event, sender: .mara, body: "marked the sale paid", saleNumber: 48_213, createdAt: .stub),
|
||
],
|
||
saleNumber: 48_213
|
||
),
|
||
now: .stub
|
||
).render()
|
||
|
||
#expect(html.contains("Kestrel Row Comics accepted the offer · today, 09:14"))
|
||
#expect(html.contains("You marked the sale paid"))
|
||
#expect(html.components(separatedBy: "Sale record CCN-48213 created").count - 1 == 1)
|
||
#expect(html.contains("Agreed · CCN-48213"))
|
||
#expect(html.contains(">Accepted<"))
|
||
}
|
||
|
||
@Test
|
||
func `messages list the side asked for, counting what is unread on each`() {
|
||
let conversations: [Conversation] = [
|
||
.stub(side: .buying, entries: [.text("Shipping Thursday.", from: .joost)], unreadCount: 2),
|
||
.stub(side: .selling, entries: [.text("Still available?", from: .mara)], unreadCount: 1),
|
||
]
|
||
let buying = MessagesPage(conversations: conversations, now: .stub).render()
|
||
let selling = MessagesPage(conversations: conversations, box: .selling, now: .stub).render()
|
||
|
||
#expect(buying.contains("2 unread"))
|
||
#expect(buying.contains(#"href="/messages?box=buying" aria-current="true">Buying · 1"#))
|
||
#expect(buying.contains("Shipping Thursday."))
|
||
#expect(!buying.contains("Still available?"))
|
||
#expect(selling.contains("Still available?"))
|
||
#expect(selling.contains(">MV<"))
|
||
#expect(buying.contains("Pick a conversation."))
|
||
}
|
||
|
||
// MARK: SellerDashboardPage tests
|
||
|
||
@Test
|
||
func `seller dashboard lists what waits for the seller`() {
|
||
let html = SellerDashboardPage(
|
||
shop: .stub(),
|
||
offers: [.stub()],
|
||
sales: [.stub(paid: true), .stub(paid: true, sent: true)],
|
||
listings: [.stub()],
|
||
viewer: .init(displayName: "Joost de Wit", shopSlug: "kestrel-row-comics", unreadMessages: 2),
|
||
now: .stub
|
||
).render()
|
||
|
||
#expect(html.contains("Hello, Joost"))
|
||
#expect(html.contains("1 offer waiting · 1 sale to dispatch · 2 unread"))
|
||
#expect(html.contains("from Mara Visser"))
|
||
#expect(html.contains(#"name="return" value="/sell""#))
|
||
#expect(html.contains(#"href="/sales/48213""#))
|
||
#expect(html.contains("1 awaiting your dispatch"))
|
||
#expect(html.contains(#"handshake__step handshake__step--done">Paid"#))
|
||
}
|
||
|
||
@Test
|
||
func `seller dashboard says so when nothing waits`() {
|
||
let html = SellerDashboardPage(shop: .stub(), offers: [], sales: [], listings: [], now: .stub).render()
|
||
|
||
#expect(html.contains("No offers waiting."))
|
||
#expect(html.contains("No sales yet."))
|
||
}
|
||
|
||
// MARK: ListingPage tests
|
||
|
||
@Test
|
||
func `listing page closes its actions once sold, and to its own seller`() {
|
||
let sold = ListingPage(listing: .stub(soldAt: .stub), related: []).render()
|
||
let own = ListingPage(
|
||
listing: .stub(),
|
||
related: [],
|
||
viewer: .init(displayName: "Joost", shopSlug: "kestrel-row-comics")
|
||
).render()
|
||
|
||
#expect(sold.contains("This copy sold on 23 Sep."))
|
||
#expect(sold.contains("Kestrel Row Comics has 0 more copies of Nightward #1, and 0 are listed across CCN."))
|
||
#expect(sold.contains(#"href="/wants/new?title=Nightward%20%231""#))
|
||
#expect(!sold.contains("Buy now"))
|
||
#expect(own.contains("This is your listing."))
|
||
#expect(!own.contains("Make an offer"))
|
||
}
|
||
|
||
// MARK: Shared tests
|
||
|
||
@Test
|
||
func `no trade page renders an inline style`() {
|
||
let pages = [
|
||
OfferPage(listing: .stub(), mode: .offer).render(),
|
||
SalePage(sale: .stub(), role: .buyer, now: .stub).render(),
|
||
MessagesPage(conversations: [.stub()], open: .stub(), now: .stub).render(),
|
||
SellerDashboardPage(shop: .stub(), offers: [.stub()], sales: [.stub()], listings: [.stub()], now: .stub).render(),
|
||
]
|
||
|
||
for html in pages {
|
||
#expect(!html.contains("style="))
|
||
#expect(!html.contains("<style"))
|
||
#expect(html.contains("/css/trade.css"))
|
||
}
|
||
}
|
||
|
||
}
|