Skip to content

Commit 860480c

Browse files
authored
feat(EMI-2830): display order2 artsy shipping options (#16172)
* feat: display artsy shipping options * add white glove discription * remove margin * add tests * update shipping time estimates
1 parent 438254b commit 860480c

File tree

4 files changed

+512
-5
lines changed

4 files changed

+512
-5
lines changed

src/Apps/Order2/Routes/Checkout/Components/DeliveryOptionsStep/Order2DeliveryOptionsForm.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,38 @@ const MultipleShippingOptionsForm = ({
190190
setFieldValue("deliveryOption", selected)
191191
}}
192192
>
193-
{options.map((option, i) => (
194-
<Radio key={`${option.type}:${i}`} value={option}>
195-
{option.type} - {option.amount?.display}
196-
</Radio>
197-
))}
193+
{options.map((option, i) => {
194+
const label = deliveryOptionLabel(option.type)
195+
const timeEstimate = deliveryOptionTimeEstimate(option.type)
196+
const [prefix, timeRange] = timeEstimate || []
197+
198+
return (
199+
<Radio
200+
label={label}
201+
key={`${option.type}:${i}`}
202+
value={option}
203+
mt={2}
204+
>
205+
<Flex justifyContent="space-between" width="100%">
206+
<Flex flexDirection="column">
207+
{timeEstimate && (
208+
<Text variant="xs" color="mono60">
209+
{prefix} <strong>{timeRange}</strong>
210+
</Text>
211+
)}
212+
{option.type === "ARTSY_WHITE_GLOVE" &&
213+
selectedOption === option && (
214+
<Text variant="xs" color="mono60">
215+
This service includes custom packing, transportation on
216+
a fine art shuttle, and in-home delivery.
217+
</Text>
218+
)}
219+
</Flex>
220+
<Text>{option.amount?.display}</Text>
221+
</Flex>
222+
</Radio>
223+
)
224+
})}
198225
</RadioGroup>
199226
</Flex>
200227
)
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { screen } from "@testing-library/react"
2+
import userEvent from "@testing-library/user-event"
3+
import { setupTestWrapperTL } from "DevTools/setupTestWrapperTL"
4+
import { graphql } from "react-relay"
5+
import { Order2DeliveryOptionsForm } from "../Order2DeliveryOptionsForm"
6+
7+
jest.unmock("react-relay")
8+
9+
const mockCheckoutContext: any = {
10+
checkoutTracking: {
11+
clickedOrderProgression: jest.fn(),
12+
clickedBuyerProtection: jest.fn(),
13+
},
14+
setDeliveryOptionComplete: jest.fn(),
15+
}
16+
17+
jest.mock("Apps/Order2/Routes/Checkout/Hooks/useCheckoutContext", () => ({
18+
useCheckoutContext: () => mockCheckoutContext,
19+
}))
20+
21+
const mockSetOrderFulfillmentOption = jest.fn()
22+
23+
jest.mock(
24+
"Apps/Order2/Routes/Checkout/Mutations/useOrder2SetOrderFulfillmentOptionMutation",
25+
() => ({
26+
useOrder2SetOrderFulfillmentOptionMutation: () => ({
27+
submitMutation: mockSetOrderFulfillmentOption,
28+
}),
29+
}),
30+
)
31+
32+
beforeEach(() => {
33+
jest.clearAllMocks()
34+
})
35+
36+
const { renderWithRelay } = setupTestWrapperTL({
37+
Component: (props: any) => (
38+
<Order2DeliveryOptionsForm order={props.me.order} />
39+
),
40+
query: graphql`
41+
query Order2DeliveryOptionsFormTestQuery @relay_test_operation {
42+
me {
43+
order(id: "order-id") {
44+
...Order2DeliveryOptionsForm_order
45+
}
46+
}
47+
}
48+
`,
49+
})
50+
51+
describe("Order2DeliveryOptionsForm", () => {
52+
describe("with single delivery option", () => {
53+
it("renders single shipping option without radio buttons", () => {
54+
renderWithRelay({
55+
Me: () => ({
56+
order: {
57+
internalID: "order-123",
58+
fulfillmentOptions: [
59+
{
60+
type: "ARTSY_STANDARD",
61+
amount: { display: "$25.00" },
62+
selected: true,
63+
},
64+
],
65+
},
66+
}),
67+
})
68+
69+
expect(screen.getByText("Shipping method")).toBeInTheDocument()
70+
expect(screen.getByText("Standard")).toBeInTheDocument()
71+
expect(screen.getByText("$25.00")).toBeInTheDocument()
72+
expect(screen.getByText("Continue to Payment")).toBeInTheDocument()
73+
74+
expect(screen.queryByRole("radio")).not.toBeInTheDocument()
75+
})
76+
77+
it("displays buyer guarantee link", () => {
78+
renderWithRelay({
79+
Me: () => ({
80+
order: {
81+
internalID: "order-123",
82+
fulfillmentOptions: [
83+
{
84+
type: "ARTSY_STANDARD",
85+
amount: { display: "$25.00" },
86+
selected: true,
87+
},
88+
],
89+
},
90+
}),
91+
})
92+
93+
const guaranteeLink = screen.getByRole("link")
94+
expect(guaranteeLink).toBeInTheDocument()
95+
expect(guaranteeLink).toHaveAttribute("target", "_blank")
96+
expect(guaranteeLink).toHaveAttribute(
97+
"href",
98+
"https://support.artsy.net/s/article/The-Artsy-Guarantee",
99+
)
100+
})
101+
})
102+
103+
describe("with multiple delivery options", () => {
104+
const multipleOptionsData = {
105+
Me: () => ({
106+
order: {
107+
internalID: "order-123",
108+
fulfillmentOptions: [
109+
{
110+
type: "ARTSY_STANDARD",
111+
amount: { display: "$25.00" },
112+
selected: true,
113+
},
114+
{
115+
type: "ARTSY_EXPRESS",
116+
amount: { display: "$50.00" },
117+
selected: false,
118+
},
119+
{
120+
type: "ARTSY_WHITE_GLOVE",
121+
amount: { display: "$200.00" },
122+
selected: false,
123+
},
124+
],
125+
},
126+
}),
127+
}
128+
129+
it("renders radio group with multiple shipping options", () => {
130+
renderWithRelay(multipleOptionsData)
131+
132+
const radios = screen.getAllByRole("radio")
133+
expect(radios).toHaveLength(3)
134+
135+
expect(screen.getByText("Standard")).toBeInTheDocument()
136+
expect(screen.getByText("Express")).toBeInTheDocument()
137+
expect(screen.getByText("White Glove")).toBeInTheDocument()
138+
139+
expect(screen.getByText("$25.00")).toBeInTheDocument()
140+
expect(screen.getByText("$50.00")).toBeInTheDocument()
141+
expect(screen.getByText("$200.00")).toBeInTheDocument()
142+
})
143+
144+
it("displays time estimates for delivery options", () => {
145+
renderWithRelay(multipleOptionsData)
146+
147+
const timeEstimates = screen.getAllByText(/Estimated to deliver between/)
148+
expect(timeEstimates.length).toBeGreaterThan(0)
149+
})
150+
151+
it("shows white glove description only when white glove is selected", async () => {
152+
renderWithRelay(multipleOptionsData)
153+
154+
expect(
155+
screen.queryByText(
156+
/custom packing, transportation on a fine art shuttle/,
157+
),
158+
).not.toBeInTheDocument()
159+
160+
const whiteGloveRadio = screen.getByRole("radio", { name: /White Glove/ })
161+
await userEvent.click(whiteGloveRadio)
162+
163+
expect(
164+
screen.getByText(
165+
/custom packing, transportation on a fine art shuttle/,
166+
),
167+
).toBeInTheDocument()
168+
})
169+
170+
it("allows selecting different delivery options", async () => {
171+
renderWithRelay(multipleOptionsData)
172+
173+
const standardRadio = screen.getByRole("radio", { name: /Standard/ })
174+
const expressRadio = screen.getByRole("radio", { name: /Express/ })
175+
176+
expect(standardRadio).toBeChecked()
177+
178+
await userEvent.click(expressRadio)
179+
180+
expect(expressRadio).toBeChecked()
181+
expect(standardRadio).not.toBeChecked()
182+
})
183+
})
184+
185+
describe("filtering pickup options", () => {
186+
it("filters out PICKUP options from delivery options", () => {
187+
renderWithRelay({
188+
Me: () => ({
189+
order: {
190+
internalID: "order-123",
191+
fulfillmentOptions: [
192+
{
193+
type: "ARTSY_STANDARD",
194+
amount: { display: "$25.00" },
195+
selected: true,
196+
},
197+
{
198+
type: "PICKUP",
199+
amount: { display: "$0.00" },
200+
selected: false,
201+
},
202+
],
203+
},
204+
}),
205+
})
206+
207+
expect(screen.getByText("Standard")).toBeInTheDocument()
208+
expect(screen.queryByText("Pickup")).not.toBeInTheDocument()
209+
})
210+
})
211+
})

src/Apps/Order2/Routes/Checkout/Components/DeliveryOptionsStep/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ export const deliveryOptionLabel = (type?: string | null) => {
99
return "Flat rate"
1010
case "INTERNATIONAL_FLAT":
1111
return "Flat rate"
12+
case "ARTSY_STANDARD":
13+
return "Standard"
14+
case "ARTSY_EXPRESS":
15+
return "Express"
16+
case "ARTSY_WHITE_GLOVE":
17+
return "White Glove"
1218
default:
1319
return `(TODO) ${type?.replace(/_/g, " ").toLocaleLowerCase()}`
1420
}
@@ -58,6 +64,33 @@ export const deliveryOptionTimeEstimate = (
5864
endOffsetDays: 10,
5965
}),
6066
]
67+
case "ARTSY_STANDARD":
68+
return [
69+
"Estimated to deliver between",
70+
dateRangeString({
71+
from,
72+
startOffsetDays: 8,
73+
endOffsetDays: 11,
74+
}),
75+
]
76+
case "ARTSY_EXPRESS":
77+
return [
78+
"Estimated to deliver between",
79+
dateRangeString({
80+
from,
81+
startOffsetDays: 8,
82+
endOffsetDays: 9,
83+
}),
84+
]
85+
case "ARTSY_WHITE_GLOVE":
86+
return [
87+
"Estimated to deliver between",
88+
dateRangeString({
89+
from,
90+
startOffsetDays: 14,
91+
endOffsetDays: 56,
92+
}),
93+
]
6194
default:
6295
return [
6396
"Estimated to ship between",

0 commit comments

Comments
 (0)