564 lines
23 KiB
JavaScript
564 lines
23 KiB
JavaScript
|
|
const { By, until } = require('selenium-webdriver');
|
||
|
|
const RegistrationPage = require('../../support/pages/RegistrationPage');
|
||
|
|
const LoginPage = require('../../support/pages/LoginPage');
|
||
|
|
const DashboardPage = require('../../support/pages/DashboardPage');
|
||
|
|
const TripPage = require('../../support/pages/TripPage');
|
||
|
|
|
||
|
|
describe('Timeline Scheduling Feature Test', () => {
|
||
|
|
let driver;
|
||
|
|
let registrationPage;
|
||
|
|
let loginPage;
|
||
|
|
let dashboardPage;
|
||
|
|
let tripPage;
|
||
|
|
let testUser;
|
||
|
|
let testTrip;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
driver = await global.createDriver();
|
||
|
|
registrationPage = new RegistrationPage(driver);
|
||
|
|
loginPage = new LoginPage(driver);
|
||
|
|
dashboardPage = new DashboardPage(driver);
|
||
|
|
tripPage = new TripPage(driver);
|
||
|
|
|
||
|
|
// Create unique test data
|
||
|
|
const timestamp = Date.now();
|
||
|
|
testUser = {
|
||
|
|
name: `Timeline Test User ${timestamp}`,
|
||
|
|
email: `timeline.test.${timestamp}@example.com`,
|
||
|
|
password: 'TimelineTest123!'
|
||
|
|
};
|
||
|
|
|
||
|
|
testTrip = {
|
||
|
|
name: `Timeline Test Trip ${timestamp}`,
|
||
|
|
description: 'Testing timeline scheduling feature',
|
||
|
|
startDate: '2025-03-01',
|
||
|
|
endDate: '2025-03-03'
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
global.timelineTestsInitialized = false;
|
||
|
|
await global.quitDriver(driver);
|
||
|
|
});
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
// For individual test runs or when starting fresh, ensure we have proper setup
|
||
|
|
if (!global.timelineTestsInitialized) {
|
||
|
|
// Clear storage and cookies
|
||
|
|
await Promise.all([
|
||
|
|
driver.manage().deleteAllCookies().catch(() => {}),
|
||
|
|
driver.executeScript('try { localStorage.clear(); sessionStorage.clear(); } catch(e) {}')
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Navigate to base URL
|
||
|
|
await driver.get(process.env.APP_URL || 'http://localhost:5173');
|
||
|
|
await driver.wait(until.urlContains('/'), 5000);
|
||
|
|
|
||
|
|
// Register new user
|
||
|
|
try {
|
||
|
|
await driver.wait(until.elementLocated(By.css('.auth-toggle button:last-child')), 2000);
|
||
|
|
await driver.findElement(By.css('.auth-toggle button:last-child')).click();
|
||
|
|
|
||
|
|
await registrationPage.register(testUser.name, testUser.email, testUser.password);
|
||
|
|
|
||
|
|
// Wait for dashboard to load
|
||
|
|
await driver.wait(until.elementLocated(By.className('dashboard')), 10000);
|
||
|
|
await driver.wait(until.elementLocated(By.css('.add-trip-card')), 10000);
|
||
|
|
|
||
|
|
// Create a trip
|
||
|
|
const addTripCard = await driver.findElement(By.css('.add-trip-card'));
|
||
|
|
await addTripCard.click();
|
||
|
|
await driver.wait(until.elementLocated(By.css('.trip-modal')), 10000);
|
||
|
|
|
||
|
|
await tripPage.fillTripForm(testTrip);
|
||
|
|
await tripPage.submitTripForm();
|
||
|
|
|
||
|
|
// Wait for trip to be created
|
||
|
|
await driver.wait(until.elementLocated(By.xpath(`//h3[contains(text(), "${testTrip.name}")]`)), 10000);
|
||
|
|
|
||
|
|
global.timelineTestsInitialized = true;
|
||
|
|
} catch (e) {
|
||
|
|
console.log('Failed to initialize timeline test environment:', e.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Helper function to navigate to trip detail page
|
||
|
|
async function navigateToTripDetail() {
|
||
|
|
const currentUrl = await driver.getCurrentUrl();
|
||
|
|
|
||
|
|
if (global.timelineTripDetailUrl && currentUrl === global.timelineTripDetailUrl) {
|
||
|
|
await driver.wait(until.elementLocated(By.className('trip-timeline')), 10000);
|
||
|
|
await driver.sleep(500);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (global.timelineTripDetailUrl) {
|
||
|
|
await driver.get(global.timelineTripDetailUrl);
|
||
|
|
await driver.sleep(1000);
|
||
|
|
} else {
|
||
|
|
await driver.wait(until.elementLocated(By.className('dashboard')), 10000);
|
||
|
|
const tripCard = await driver.findElement(By.xpath(`//h3[contains(text(), "${testTrip.name}")]/ancestor::div[contains(@class, 'trip-card')]`));
|
||
|
|
await tripCard.click();
|
||
|
|
await driver.wait(until.urlContains('/trip/'), 10000);
|
||
|
|
global.timelineTripDetailUrl = await driver.getCurrentUrl();
|
||
|
|
}
|
||
|
|
|
||
|
|
await driver.wait(until.elementLocated(By.className('trip-timeline')), 10000);
|
||
|
|
await driver.sleep(500);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper function to create a plannable item
|
||
|
|
async function createPlannableItem(itemData) {
|
||
|
|
// Click Add Item button in sidebar
|
||
|
|
const addItemButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]'));
|
||
|
|
await addItemButton.click();
|
||
|
|
|
||
|
|
// Wait for form modal
|
||
|
|
await driver.wait(until.elementLocated(By.className('plannable-form-modal')), 5000);
|
||
|
|
|
||
|
|
// Fill in the form
|
||
|
|
await driver.findElement(By.name('name')).sendKeys(itemData.name);
|
||
|
|
|
||
|
|
if (itemData.type) {
|
||
|
|
const typeSelect = await driver.findElement(By.name('type'));
|
||
|
|
await typeSelect.findElement(By.xpath(`//option[@value="${itemData.type}"]`)).click();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (itemData.address) {
|
||
|
|
await driver.findElement(By.name('address')).sendKeys(itemData.address);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (itemData.notes) {
|
||
|
|
await driver.findElement(By.name('notes')).sendKeys(itemData.notes);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Submit without assigning to a slot (leave in "Unplanned Items")
|
||
|
|
const submitButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]'));
|
||
|
|
await driver.executeScript("arguments[0].scrollIntoView(true);", submitButton);
|
||
|
|
await driver.sleep(500);
|
||
|
|
await driver.executeScript("arguments[0].click();", submitButton);
|
||
|
|
|
||
|
|
// Wait for modal to close
|
||
|
|
await driver.sleep(1500);
|
||
|
|
await driver.wait(async () => {
|
||
|
|
const overlays = await driver.findElements(By.css('.plannable-form-overlay'));
|
||
|
|
for (const overlay of overlays) {
|
||
|
|
try {
|
||
|
|
if (await overlay.isDisplayed()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
// Stale element
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}, 10000);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper to wait for modal close
|
||
|
|
async function waitForModalClose() {
|
||
|
|
await driver.sleep(1000);
|
||
|
|
await driver.wait(async () => {
|
||
|
|
const modals = await driver.findElements(By.css('.modal, .base-modal'));
|
||
|
|
for (const modal of modals) {
|
||
|
|
try {
|
||
|
|
if (await modal.isDisplayed()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
// Stale element
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}, 10000);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('Timeline Display', () => {
|
||
|
|
it('should display timeline with correct structure', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Verify timeline container exists
|
||
|
|
const timeline = await driver.findElement(By.className('trip-timeline'));
|
||
|
|
expect(await timeline.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
// Verify timeline has header
|
||
|
|
const timelineHeader = await driver.findElement(By.xpath('//h3[contains(text(), "Trip Timeline")]'));
|
||
|
|
expect(await timelineHeader.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should display all days of the trip', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Trip is from March 1-3, so we should have 3 day sections
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
expect(daySections.length).toBe(3);
|
||
|
|
|
||
|
|
// Verify day headers
|
||
|
|
const day1Header = await driver.findElement(By.xpath('//h4[contains(text(), "Day 1")]'));
|
||
|
|
expect(await day1Header.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
const day2Header = await driver.findElement(By.xpath('//h4[contains(text(), "Day 2")]'));
|
||
|
|
expect(await day2Header.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
const day3Header = await driver.findElement(By.xpath('//h4[contains(text(), "Day 3")]'));
|
||
|
|
expect(await day3Header.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should display hour rows for each day', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Get first day section
|
||
|
|
const firstDaySection = await driver.findElement(By.className('day-section'));
|
||
|
|
|
||
|
|
// Should have hour rows (6 AM to 11 PM = 18 hours)
|
||
|
|
const hourRows = await firstDaySection.findElements(By.className('hour-row'));
|
||
|
|
expect(hourRows.length).toBeGreaterThanOrEqual(18);
|
||
|
|
|
||
|
|
// Verify specific hour labels exist
|
||
|
|
const hour6am = await driver.findElement(By.xpath('//div[contains(@class, "hour-label") and contains(text(), "6:00 AM")]'));
|
||
|
|
expect(await hour6am.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
const hour11pm = await driver.findElement(By.xpath('//div[contains(@class, "hour-label") and contains(text(), "11:00 PM")]'));
|
||
|
|
expect(await hour11pm.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Scheduling Items via Timeline', () => {
|
||
|
|
it('should show + button on hover over hour row', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Find an hour row
|
||
|
|
const hourRow = await driver.findElement(By.className('hour-row'));
|
||
|
|
|
||
|
|
// Hover over it
|
||
|
|
await driver.actions().move({ origin: hourRow }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
|
||
|
|
// Should see a + button
|
||
|
|
const addButton = await hourRow.findElement(By.css('button'));
|
||
|
|
expect(await addButton.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should open schedule modal when clicking + button', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// First create a plannable item to schedule
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Breakfast at Cafe',
|
||
|
|
type: 'restaurant',
|
||
|
|
notes: 'Morning coffee'
|
||
|
|
});
|
||
|
|
|
||
|
|
// Now find the 8 AM hour row
|
||
|
|
const hour8am = await driver.findElement(By.xpath('//div[contains(@class, "hour-label") and contains(text(), "8:00 AM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
// Hover to show + button
|
||
|
|
await driver.actions().move({ origin: hour8am }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
|
||
|
|
// Click the + button
|
||
|
|
const addButton = await hour8am.findElement(By.css('button'));
|
||
|
|
await addButton.click();
|
||
|
|
|
||
|
|
// Wait for schedule modal to appear
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
// Verify modal elements
|
||
|
|
const itemSelect = await driver.findElement(By.css('select'));
|
||
|
|
expect(await itemSelect.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
// Should have time pickers
|
||
|
|
const timeInputs = await driver.findElements(By.css('select'));
|
||
|
|
expect(timeInputs.length).toBeGreaterThanOrEqual(2); // Start time and end time
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should schedule an item at specific time', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Create plannable item
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Louvre Museum Visit',
|
||
|
|
type: 'attraction',
|
||
|
|
notes: 'Morning visit to see Mona Lisa'
|
||
|
|
});
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Find the 10 AM hour row on Day 1
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
const day1Section = daySections[0];
|
||
|
|
|
||
|
|
const hour10am = await day1Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "10:00 AM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
// Hover and click +
|
||
|
|
await driver.actions().move({ origin: hour10am }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
|
||
|
|
const addButton = await hour10am.findElement(By.css('button'));
|
||
|
|
await addButton.click();
|
||
|
|
|
||
|
|
// Wait for modal
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
// Select the item
|
||
|
|
const itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Louvre Museum Visit")]')).click();
|
||
|
|
|
||
|
|
// Start time should default to 10:00
|
||
|
|
const startTimeSelect = await driver.findElement(By.xpath('//label[contains(text(), "Start Time")]/following-sibling::select'));
|
||
|
|
const startTimeValue = await startTimeSelect.getAttribute('value');
|
||
|
|
expect(startTimeValue).toBe('10:00');
|
||
|
|
|
||
|
|
// Set end time to 12:00
|
||
|
|
const endTimeSelect = await driver.findElement(By.xpath('//label[contains(text(), "End Time")]/following-sibling::select'));
|
||
|
|
await endTimeSelect.findElement(By.xpath('//option[@value="12:00"]')).click();
|
||
|
|
|
||
|
|
// Submit
|
||
|
|
const scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
|
||
|
|
// Wait for modal to close
|
||
|
|
await waitForModalClose();
|
||
|
|
|
||
|
|
// Verify item appears in timeline at 10 AM
|
||
|
|
await driver.sleep(1000);
|
||
|
|
const scheduledItem = await driver.findElement(By.xpath('//div[contains(@class, "scheduled-slot") and contains(., "Louvre Museum Visit")]'));
|
||
|
|
expect(await scheduledItem.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
// Verify time display shows 10:00 AM - 12:00 PM
|
||
|
|
const timeDisplay = await scheduledItem.getText();
|
||
|
|
expect(timeDisplay).toContain('10:00');
|
||
|
|
expect(timeDisplay).toContain('12:00');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should schedule multiple items on the same day', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Create multiple plannable items
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Morning Croissant',
|
||
|
|
type: 'restaurant'
|
||
|
|
});
|
||
|
|
|
||
|
|
await driver.sleep(500);
|
||
|
|
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Afternoon Tea',
|
||
|
|
type: 'restaurant'
|
||
|
|
});
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Schedule first item at 8 AM
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
const day2Section = daySections[1]; // Day 2
|
||
|
|
|
||
|
|
const hour8am = await day2Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "8:00 AM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
await driver.actions().move({ origin: hour8am }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour8am.findElement(By.css('button')).click();
|
||
|
|
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
const itemSelect1 = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect1.findElement(By.xpath('//option[contains(text(), "Morning Croissant")]')).click();
|
||
|
|
|
||
|
|
const scheduleButton1 = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton1.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Schedule second item at 3 PM
|
||
|
|
const hour3pm = await day2Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "3:00 PM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
await driver.actions().move({ origin: hour3pm }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour3pm.findElement(By.css('button')).click();
|
||
|
|
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
const itemSelect2 = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect2.findElement(By.xpath('//option[contains(text(), "Afternoon Tea")]')).click();
|
||
|
|
|
||
|
|
const endTimeSelect = await driver.findElement(By.xpath('//label[contains(text(), "End Time")]/following-sibling::select'));
|
||
|
|
await endTimeSelect.findElement(By.xpath('//option[@value="16:00"]')).click();
|
||
|
|
|
||
|
|
const scheduleButton2 = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton2.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Verify both items appear in Day 2 section
|
||
|
|
const croissantItem = await day2Section.findElement(By.xpath('.//div[contains(., "Morning Croissant")]'));
|
||
|
|
expect(await croissantItem.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
const teaItem = await day2Section.findElement(By.xpath('.//div[contains(., "Afternoon Tea")]'));
|
||
|
|
expect(await teaItem.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should validate end time is after start time', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Create plannable item
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Invalid Time Test',
|
||
|
|
type: 'other'
|
||
|
|
});
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Open schedule modal at 2 PM
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
const day3Section = daySections[2]; // Day 3
|
||
|
|
|
||
|
|
const hour2pm = await day3Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "2:00 PM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
await driver.actions().move({ origin: hour2pm }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour2pm.findElement(By.css('button')).click();
|
||
|
|
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
// Select item
|
||
|
|
const itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Invalid Time Test")]')).click();
|
||
|
|
|
||
|
|
// Try to set end time before start time (e.g., start 14:00, end 13:00)
|
||
|
|
const endTimeSelect = await driver.findElement(By.xpath('//label[contains(text(), "End Time")]/following-sibling::select'));
|
||
|
|
await endTimeSelect.findElement(By.xpath('//option[@value="13:00"]')).click();
|
||
|
|
|
||
|
|
// Submit
|
||
|
|
const scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Should show error message
|
||
|
|
const errorMessage = await driver.findElement(By.xpath('//*[contains(text(), "End time must be after start time")]'));
|
||
|
|
expect(await errorMessage.isDisplayed()).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Timeline Integration', () => {
|
||
|
|
it('should display item in both sidebar and timeline after scheduling', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Create plannable item
|
||
|
|
await createPlannableItem({
|
||
|
|
name: 'Arc de Triomphe',
|
||
|
|
type: 'attraction'
|
||
|
|
});
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Verify it appears in sidebar under "Unplanned Items"
|
||
|
|
const sidebarItem = await driver.findElement(By.xpath('//h4[contains(text(), "Arc de Triomphe")]'));
|
||
|
|
expect(await sidebarItem.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
// Schedule it via timeline
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
const day1Section = daySections[0];
|
||
|
|
|
||
|
|
const hour11am = await day1Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "11:00 AM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
|
||
|
|
await driver.actions().move({ origin: hour11am }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour11am.findElement(By.css('button')).click();
|
||
|
|
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
|
||
|
|
const itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Arc de Triomphe")]')).click();
|
||
|
|
|
||
|
|
const scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Verify it now appears in timeline
|
||
|
|
const timelineItem = await driver.findElement(By.xpath('//div[contains(@class, "scheduled-slot") and contains(., "Arc de Triomphe")]'));
|
||
|
|
expect(await timelineItem.isDisplayed()).toBe(true);
|
||
|
|
|
||
|
|
// Note: The sidebar behavior might change - item could stay in unplanned or move to a day section
|
||
|
|
// For now, we just verify it's in the timeline
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should maintain chronological order when items are scheduled out of order', async () => {
|
||
|
|
await navigateToTripDetail();
|
||
|
|
|
||
|
|
// Create items
|
||
|
|
await createPlannableItem({ name: 'Dinner', type: 'restaurant' });
|
||
|
|
await driver.sleep(500);
|
||
|
|
await createPlannableItem({ name: 'Breakfast', type: 'restaurant' });
|
||
|
|
await driver.sleep(500);
|
||
|
|
await createPlannableItem({ name: 'Lunch', type: 'restaurant' });
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
const daySections = await driver.findElements(By.className('day-section'));
|
||
|
|
const day1Section = daySections[0];
|
||
|
|
|
||
|
|
// Schedule Dinner first (7 PM)
|
||
|
|
const hour7pm = await day1Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "7:00 PM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
await driver.actions().move({ origin: hour7pm }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour7pm.findElement(By.css('button')).click();
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
let itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Dinner")]')).click();
|
||
|
|
let scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Schedule Breakfast (7 AM)
|
||
|
|
const hour7am = await day1Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "7:00 AM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
await driver.actions().move({ origin: hour7am }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour7am.findElement(By.css('button')).click();
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Breakfast")]')).click();
|
||
|
|
scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Schedule Lunch (12 PM)
|
||
|
|
const hour12pm = await day1Section.findElement(By.xpath('.//div[contains(@class, "hour-label") and contains(text(), "12:00 PM")]/following-sibling::div[contains(@class, "hour-content")]'));
|
||
|
|
await driver.actions().move({ origin: hour12pm }).perform();
|
||
|
|
await driver.sleep(500);
|
||
|
|
await hour12pm.findElement(By.css('button')).click();
|
||
|
|
await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "Schedule Item")]')), 5000);
|
||
|
|
itemSelect = await driver.findElement(By.xpath('//label[contains(text(), "Select Item")]/following-sibling::select'));
|
||
|
|
await itemSelect.findElement(By.xpath('//option[contains(text(), "Lunch")]')).click();
|
||
|
|
scheduleButton = await driver.findElement(By.xpath('//button[contains(text(), "Schedule")]'));
|
||
|
|
await scheduleButton.click();
|
||
|
|
await waitForModalClose();
|
||
|
|
await driver.sleep(1000);
|
||
|
|
|
||
|
|
// Verify items appear in chronological order in Day 1 section
|
||
|
|
const scheduledSlots = await day1Section.findElements(By.className('scheduled-slot'));
|
||
|
|
|
||
|
|
// Get text of all scheduled items to verify order
|
||
|
|
const itemTexts = [];
|
||
|
|
for (const slot of scheduledSlots) {
|
||
|
|
const text = await slot.getText();
|
||
|
|
itemTexts.push(text);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Breakfast should appear before Lunch, and Lunch before Dinner
|
||
|
|
const breakfastIndex = itemTexts.findIndex(text => text.includes('Breakfast'));
|
||
|
|
const lunchIndex = itemTexts.findIndex(text => text.includes('Lunch'));
|
||
|
|
const dinnerIndex = itemTexts.findIndex(text => text.includes('Dinner'));
|
||
|
|
|
||
|
|
expect(breakfastIndex).toBeLessThan(lunchIndex);
|
||
|
|
expect(lunchIndex).toBeLessThan(dinnerIndex);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|