Merged in feat/sw-2879-booking-widget-to-booking-flow-package (pull request #2532)
feat(SW-2879): Move BookingWidget to booking-flow package * Fix lockfile * Fix styling * a tiny little booking widget test * Tiny fixes * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Remove unused scripts * lint:fix * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Tiny lint fixes * update test * Update Input in booking-flow * Clean up comments etc * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Setup tracking context for booking-flow * Add missing use client * Fix temp tracking function * Pass booking to booking-widget * Remove comment * Add use client to booking widget tracking provider * Add use client to tracking functions * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Move debug page * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package * Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package Approved-by: Bianca Widstam
This commit is contained in:
53
apps/partner-sas/app/[lang]/debug/page.tsx
Normal file
53
apps/partner-sas/app/[lang]/debug/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { BookingWidget } from "@scandic-hotels/booking-flow/BookingWidget"
|
||||
import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { serverClient } from "@/lib/trpc"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import { ClientComponent } from "../../../components/ClientComponent"
|
||||
|
||||
type SearchParams<S = {}> = {
|
||||
searchParams: Promise<S & { [key: string]: string }>
|
||||
}
|
||||
|
||||
export default async function Debug(props: SearchParams) {
|
||||
const searchParams = await props.searchParams
|
||||
const intl = await getIntl()
|
||||
const lang = await getLang()
|
||||
const caller = await serverClient()
|
||||
const destinations = await caller.autocomplete.destinations({
|
||||
lang,
|
||||
includeTypes: ["hotels"],
|
||||
query: "Göteborg",
|
||||
})
|
||||
const hotel = destinations.hits.hotels[0].name
|
||||
|
||||
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||
|
||||
return (
|
||||
<div style={{ padding: "20px" }}>
|
||||
<Typography>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>from booking-flow package:</p>
|
||||
</Typography>
|
||||
<BookingWidget booking={booking} lang={lang} />
|
||||
<hr />
|
||||
<Typography variant="Title/Decorative/lg">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>hello world with data: {hotel}</p>
|
||||
</Typography>
|
||||
<Typography>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>
|
||||
translated:
|
||||
{intl.formatMessage({ defaultMessage: "Map of the city" })}
|
||||
</p>
|
||||
</Typography>
|
||||
<hr />
|
||||
<ClientComponent />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import "@scandic-hotels/design-system/style.css"
|
||||
import "@scandic-hotels/design-system/fonts.css"
|
||||
import "@scandic-hotels/design-system/style.css"
|
||||
import "@/public/_static/css/design-system-new-deprecated.css"
|
||||
import "../../globals.css"
|
||||
|
||||
import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { TrpcProvider } from "@scandic-hotels/trpc/Provider"
|
||||
|
||||
@@ -9,6 +11,8 @@ import { getMessages } from "@/i18n"
|
||||
import ClientIntlProvider from "@/i18n/Provider"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { trackBookingSearchClick } from "../utils/tracking"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -35,21 +39,39 @@ export default async function RootLayout(props: RootLayoutProps) {
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/_static/css/core.css" />
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/_static/css/scandic.css" />
|
||||
</head>
|
||||
<head>{/* TODO */}</head>
|
||||
<body className="scandic">
|
||||
<ClientIntlProvider
|
||||
defaultLocale={Lang.en}
|
||||
locale={params.lang}
|
||||
messages={messages}
|
||||
>
|
||||
{/* TODO handle onError */}
|
||||
<TrpcProvider>{children}</TrpcProvider>
|
||||
</ClientIntlProvider>
|
||||
<div className="root">
|
||||
<ClientIntlProvider
|
||||
defaultLocale={Lang.en}
|
||||
locale={params.lang}
|
||||
messages={messages}
|
||||
>
|
||||
{/* TODO handle onError */}
|
||||
<TrpcProvider>
|
||||
<BookingFlowTrackingProvider
|
||||
trackingFunctions={{
|
||||
trackBookingSearchClick,
|
||||
}}
|
||||
>
|
||||
<header
|
||||
style={{
|
||||
height: 64,
|
||||
backgroundColor: "dodgerblue",
|
||||
color: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<h1>SAS</h1>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
</BookingFlowTrackingProvider>
|
||||
</TrpcProvider>
|
||||
</ClientIntlProvider>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
.page {
|
||||
padding-left: 200px;
|
||||
padding-top: 200px;
|
||||
}
|
||||
@@ -1,44 +1,29 @@
|
||||
import { Temp } from "@scandic-hotels/booking-flow/test-entry"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { BookingWidget } from "@scandic-hotels/booking-flow/BookingWidget"
|
||||
import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||
|
||||
import { serverClient } from "@/lib/trpc"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import { ClientComponent } from "./ClientComponent"
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
type SearchParams<S = {}> = {
|
||||
searchParams: Promise<S & { [key: string]: string }>
|
||||
}
|
||||
|
||||
export default async function Home() {
|
||||
const intl = await getIntl()
|
||||
const caller = await serverClient()
|
||||
const destinations = await caller.autocomplete.destinations({
|
||||
lang: Lang.en,
|
||||
includeTypes: ["hotels"],
|
||||
query: "Göteborg",
|
||||
})
|
||||
const hotel = destinations.hits.hotels[0].name
|
||||
export default async function Home(props: SearchParams<{ lang: Lang }>) {
|
||||
const searchParams = await props.searchParams
|
||||
|
||||
// TODO we need this import right now to ensure configureServerClient is called,
|
||||
// but we should ensure it's called in a layout instead.
|
||||
const _caller = await serverClient()
|
||||
const lang = await getLang()
|
||||
|
||||
const booking = parseBookingWidgetSearchParams(searchParams)
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main>
|
||||
<Typography variant="Title/Decorative/lg">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>hello world with data: {hotel}</p>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<p>{intl.formatMessage({ defaultMessage: "Map of the city" })}</p>
|
||||
</Typography>
|
||||
<hr />
|
||||
<ClientComponent />
|
||||
<hr />
|
||||
<Typography>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<p>from booking-flow package:</p>
|
||||
</Typography>
|
||||
<Temp />
|
||||
</main>
|
||||
<div>
|
||||
<BookingWidget booking={booking} lang={lang} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
BIN
apps/partner-sas/app/favicon.ico
Normal file
BIN
apps/partner-sas/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
11
apps/partner-sas/app/utils/tracking.ts
Normal file
11
apps/partner-sas/app/utils/tracking.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
export function trackBookingSearchClick(
|
||||
searchTerm: string,
|
||||
searchType: "hotel" | "destination"
|
||||
) {
|
||||
console.log("TODO: Implement trackBookingSearchClick", {
|
||||
searchTerm,
|
||||
searchType,
|
||||
})
|
||||
}
|
||||
24
apps/partner-sas/components/IntlProvider.tsx
Normal file
24
apps/partner-sas/components/IntlProvider.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client"
|
||||
|
||||
import { type IntlConfig, IntlProvider } from "react-intl"
|
||||
|
||||
type ClientIntlProviderProps = React.PropsWithChildren<
|
||||
Pick<IntlConfig, "defaultLocale" | "locale" | "messages">
|
||||
>
|
||||
|
||||
export default function ClientIntlProvider({
|
||||
children,
|
||||
locale,
|
||||
defaultLocale,
|
||||
messages,
|
||||
}: ClientIntlProviderProps) {
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
defaultLocale={defaultLocale}
|
||||
messages={messages}
|
||||
>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
)
|
||||
}
|
||||
107
apps/partner-sas/globals.css
Normal file
107
apps/partner-sas/globals.css
Normal file
@@ -0,0 +1,107 @@
|
||||
:root {
|
||||
--current-max-width: 113.5rem;
|
||||
|
||||
--max-width: 94.5rem;
|
||||
--max-width-content: min(calc(100dvw - var(--max-width-spacing)), 74.75rem);
|
||||
--max-width-text-block: 49.5rem;
|
||||
--current-mobile-site-header-height: 52.41px;
|
||||
--max-width-navigation: 89.5rem;
|
||||
|
||||
--max-width-single-spacing: var(--Layout-Mobile-Margin-Margin-min);
|
||||
--max-width-spacing: calc(var(--max-width-single-spacing) * 2);
|
||||
--max-width-page: min(
|
||||
calc(100dvw - var(--max-width-spacing)),
|
||||
var(--max-width-navigation)
|
||||
);
|
||||
|
||||
--sitewide-alert-height: 0px; /* Will be overridden when a sitewide alert is visible */
|
||||
--main-menu-mobile-height: 75px;
|
||||
--main-menu-desktop-height: 125px;
|
||||
--booking-widget-mobile-height: 75px;
|
||||
--booking-widget-tablet-height: 150px;
|
||||
--booking-widget-desktop-height: 77px;
|
||||
--hotel-page-map-desktop-width: 23.75rem;
|
||||
|
||||
/* Z-INDEX */
|
||||
--header-z-index: 11;
|
||||
--menu-overlay-z-index: 11;
|
||||
--booking-widget-z-index: 10;
|
||||
--booking-widget-open-z-index: 100;
|
||||
--dialog-z-index: 9;
|
||||
--back-to-top-button: 80;
|
||||
--language-switcher-z-index: 85;
|
||||
--sidepeek-z-index: 100;
|
||||
--lightbox-z-index: 150;
|
||||
--default-modal-overlay-z-index: 100;
|
||||
--default-modal-z-index: 101;
|
||||
|
||||
--modal-box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
|
||||
--popup-box-shadow: 0 0 14px 6px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@supports (interpolate-size: allow-keywords) {
|
||||
interpolate-size: allow-keywords;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
body.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.root {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* From Tailwind */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-inline-start: 0;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
:root {
|
||||
--max-width-single-spacing: var(--Layout-Tablet-Margin-Margin-min);
|
||||
}
|
||||
|
||||
body.overflow-hidden {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
:root {
|
||||
--max-width-single-spacing: var(--Layout-Desktop-Margin-Margin-min);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,17 @@ const nextConfig: NextConfig = {
|
||||
],
|
||||
output: "standalone",
|
||||
|
||||
experimental: {
|
||||
swcPlugins: [
|
||||
[
|
||||
"@swc/plugin-formatjs",
|
||||
{
|
||||
ast: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
webpack: function (config: any) {
|
||||
config.module.rules.push(
|
||||
{
|
||||
@@ -27,17 +38,6 @@ const nextConfig: NextConfig = {
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
experimental: {
|
||||
swcPlugins: [
|
||||
[
|
||||
"@swc/plugin-formatjs",
|
||||
{
|
||||
ast: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default Sentry.withSentryConfig(nextConfig, {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@playwright/test": "^1.53.1",
|
||||
"@scandic-hotels/common": "workspace:*",
|
||||
"@scandic-hotels/typescript-config": "workspace:*",
|
||||
"@swc/plugin-formatjs": "^3.2.2",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "19.1.0",
|
||||
"@types/react-dom": "19.1.0",
|
||||
|
||||
@@ -28,25 +28,37 @@ export default defineConfig({
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:3001",
|
||||
|
||||
/* How long to wait for actions to complete. */
|
||||
actionTimeout: 15 * 1000,
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
trace: process.env.CI ? "on-first-retry" : "retain-on-failure",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
viewport: { width: 1400, height: 720 },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
viewport: { width: 1400, height: 720 },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
use: {
|
||||
...devices["Desktop Safari"],
|
||||
viewport: { width: 1400, height: 720 },
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
|
||||
1
apps/partner-sas/public/_static/icons/cancel.svg
Normal file
1
apps/partner-sas/public/_static/icons/cancel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#57514E"><path d="m336-280 144-144 144 144 56-56-144-144 144-144-56-56-144 144-144-144-56 56 144 144-144 144 56 56ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
206
apps/partner-sas/tests/booking-widget.spec.ts
Normal file
206
apps/partner-sas/tests/booking-widget.spec.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { expect, type Page, test } from "@playwright/test"
|
||||
|
||||
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
||||
|
||||
test("can make a search with city", async ({ page }) => {
|
||||
await page.goto("/")
|
||||
|
||||
// Search for city
|
||||
const combobox = page.getByRole("combobox", { name: /where to/i })
|
||||
await combobox.click()
|
||||
await combobox.fill("stockholm")
|
||||
await page.getByRole("option", { name: /stockholm sweden/i }).click()
|
||||
|
||||
// Open datepicker
|
||||
// If we had better accessibility for our datepicker this would be so much easier
|
||||
const today = new Date()
|
||||
const tomorrow = new Date(today)
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
|
||||
await page
|
||||
.getByRole("button", {
|
||||
name: `${formatDate(today)} - ${formatDate(tomorrow)}`,
|
||||
})
|
||||
.click()
|
||||
|
||||
// Select future dates
|
||||
const twoDaysFromNow = new Date(today)
|
||||
twoDaysFromNow.setDate(today.getDate() + 2)
|
||||
await clickDatePickerDate(page, twoDaysFromNow)
|
||||
|
||||
const threeDaysFromNow = new Date(today)
|
||||
threeDaysFromNow.setDate(today.getDate() + 3)
|
||||
await clickDatePickerDate(page, threeDaysFromNow)
|
||||
|
||||
await page
|
||||
.getByRole("button", {
|
||||
name: /select dates/i,
|
||||
})
|
||||
.click()
|
||||
|
||||
// Select rooms and guests
|
||||
// Once again, better accessibility would make this so much easier
|
||||
await page.getByRole("button", { name: /1 room, 1 adult/i }).click()
|
||||
const roomsDialog = page.getByRole("dialog")
|
||||
const room1section = roomsDialog.getByText(/room 1/i).locator("..")
|
||||
|
||||
// Add 1 adult
|
||||
await room1section
|
||||
.locator("section")
|
||||
.filter({ hasText: /adults/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click()
|
||||
|
||||
// Add 1 child aged 10
|
||||
await room1section
|
||||
.locator("section")
|
||||
.filter({ hasText: /children/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click()
|
||||
await room1section.getByRole("button", { name: /age/i }).click()
|
||||
await page.getByRole("option", { name: /10/i }).click()
|
||||
|
||||
await page.getByRole("button", { name: /add room/i }).click()
|
||||
|
||||
const room2section = roomsDialog.getByText(/room 2/i).locator("..")
|
||||
|
||||
// Add 2 adults
|
||||
await room2section
|
||||
.locator("section")
|
||||
.filter({ hasText: /adults/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click({ clickCount: 2 })
|
||||
|
||||
await roomsDialog.getByRole("button", { name: /done/i }).click()
|
||||
|
||||
await page.getByRole("button", { name: /search/i }).click()
|
||||
|
||||
// Assert that we navigated to the correct URL
|
||||
const expectedSearchParams = serializeBookingSearchParams({
|
||||
rooms: [
|
||||
{
|
||||
adults: 2,
|
||||
childrenInRoom: [{ age: 10, bed: ChildBedMapEnum.IN_EXTRA_BED }],
|
||||
},
|
||||
{
|
||||
adults: 3,
|
||||
childrenInRoom: [],
|
||||
},
|
||||
],
|
||||
fromDate: twoDaysFromNow.toISOString().split("T")[0],
|
||||
toDate: threeDaysFromNow.toISOString().split("T")[0],
|
||||
city: "STOCKHOLM",
|
||||
})
|
||||
|
||||
await expect(page).toHaveURL(
|
||||
`/en/hotelreservation/select-hotel?${expectedSearchParams}`
|
||||
)
|
||||
})
|
||||
|
||||
test("can make a search with hotel", async ({ page }) => {
|
||||
await page.goto("/")
|
||||
|
||||
// Search for hotel
|
||||
const combobox = page.getByRole("combobox", { name: /where to/i })
|
||||
await combobox.click()
|
||||
await combobox.fill("downtown camper")
|
||||
await page.getByRole("option", { name: /downtown camper/i }).click()
|
||||
|
||||
// Open datepicker
|
||||
// If we had better accessibility for our datepicker this would be so much easier
|
||||
const today = new Date()
|
||||
const tomorrow = new Date(today)
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
|
||||
await page
|
||||
.getByRole("button", {
|
||||
name: `${formatDate(today)} - ${formatDate(tomorrow)}`,
|
||||
})
|
||||
.click()
|
||||
|
||||
// Select future dates
|
||||
const twoDaysFromNow = new Date(today)
|
||||
twoDaysFromNow.setDate(today.getDate() + 2)
|
||||
await clickDatePickerDate(page, twoDaysFromNow)
|
||||
|
||||
const threeDaysFromNow = new Date(today)
|
||||
threeDaysFromNow.setDate(today.getDate() + 3)
|
||||
await clickDatePickerDate(page, threeDaysFromNow)
|
||||
|
||||
await page
|
||||
.getByRole("button", {
|
||||
name: /select dates/i,
|
||||
})
|
||||
.click()
|
||||
|
||||
// Select rooms and guests
|
||||
// Once again, better accessibility would make this so much easier
|
||||
await page.getByRole("button", { name: /1 room, 1 adult/i }).click()
|
||||
const roomsDialog = page.getByRole("dialog")
|
||||
const room1section = roomsDialog.getByText(/room 1/i).locator("..")
|
||||
|
||||
// Add 1 adult
|
||||
await room1section
|
||||
.locator("section")
|
||||
.filter({ hasText: /adults/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click()
|
||||
|
||||
// Add 1 child aged 10
|
||||
await room1section
|
||||
.locator("section")
|
||||
.filter({ hasText: /children/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click()
|
||||
await room1section.getByRole("button", { name: /age/i }).click()
|
||||
await page.getByRole("option", { name: /10/i }).click()
|
||||
|
||||
await page.getByRole("button", { name: /add room/i }).click()
|
||||
|
||||
const room2section = roomsDialog.getByText(/room 2/i).locator("..")
|
||||
|
||||
// Add 2 adults
|
||||
await room2section
|
||||
.locator("section")
|
||||
.filter({ hasText: /adults/i })
|
||||
.getByRole("button", { name: /add/i })
|
||||
.click({ clickCount: 2 })
|
||||
|
||||
await roomsDialog.getByRole("button", { name: /done/i }).click()
|
||||
|
||||
await page.getByRole("button", { name: /search/i }).click()
|
||||
|
||||
// Assert that we navigated to the correct URL
|
||||
const expectedSearchParams = serializeBookingSearchParams({
|
||||
rooms: [
|
||||
{
|
||||
adults: 2,
|
||||
childrenInRoom: [{ age: 10, bed: ChildBedMapEnum.IN_EXTRA_BED }],
|
||||
},
|
||||
{
|
||||
adults: 3,
|
||||
childrenInRoom: [],
|
||||
},
|
||||
],
|
||||
fromDate: twoDaysFromNow.toISOString().split("T")[0],
|
||||
toDate: threeDaysFromNow.toISOString().split("T")[0],
|
||||
hotelId: "879", // Downtown Camper
|
||||
})
|
||||
|
||||
await expect(page).toHaveURL(
|
||||
`/en/hotelreservation/select-rate?${expectedSearchParams}`
|
||||
)
|
||||
})
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
const day = date.getDate()
|
||||
const month = date.toLocaleDateString("en-US", { month: "short" })
|
||||
const weekday = date.toLocaleDateString("en-US", { weekday: "short" })
|
||||
return `${weekday}, ${day} ${month}`
|
||||
}
|
||||
|
||||
const clickDatePickerDate = async (page: Page, date: Date) => {
|
||||
const dateString = date.toISOString().split("T")[0] // YYYY-MM-DD format
|
||||
await page.locator(`[data-day="${dateString}"]`).getByRole("button").click()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
|
||||
test("has text", async ({ page }) => {
|
||||
await page.goto("/")
|
||||
|
||||
await expect(page.getByText(/hello world/i)).toBeVisible()
|
||||
})
|
||||
Reference in New Issue
Block a user