-
Notifications
You must be signed in to change notification settings - Fork 6
fix(book): loan calendar uses flatpickr on mobile (not the native picker) — shows availability #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fabiodalez-dev
wants to merge
1
commit into
main
Choose a base branch
from
fix/mobile-loan-datepicker
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+107
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| // @ts-check | ||
| // Regression: on the public book page, the loan/reservation date picker must use | ||
| // Flatpickr's OWN calendar even on mobile (disableMobile:true). Without it, | ||
| // flatpickr falls back to the native Android/iOS date picker, which ignores the | ||
| // `disable` option — so fully-booked days look selectable and availability is | ||
| // never shown. Runs under Pixel 5 (Android Chrome) emulation (mobile user-agent + touch). | ||
| const { test, expect, devices } = require('@playwright/test'); | ||
| const { execFileSync } = require('child_process'); | ||
|
|
||
| const BASE = process.env.E2E_BASE_URL || 'http://localhost:8081'; | ||
| const ADMIN_EMAIL = process.env.E2E_ADMIN_EMAIL || ''; | ||
| const ADMIN_PASS = process.env.E2E_ADMIN_PASS || ''; | ||
|
|
||
| const DB_USER = process.env.E2E_DB_USER || ''; | ||
| const DB_PASS = process.env.E2E_DB_PASS || ''; | ||
| const DB_HOST = process.env.E2E_DB_HOST || ''; | ||
| const DB_PORT = process.env.E2E_DB_PORT || ''; | ||
| const DB_SOCKET = process.env.E2E_DB_SOCKET || ''; | ||
| const DB_NAME = process.env.E2E_DB_NAME || ''; | ||
|
|
||
| test.skip( | ||
| !ADMIN_EMAIL || !ADMIN_PASS || !DB_USER || !DB_NAME, | ||
| 'E2E credentials not configured (set E2E_ADMIN_EMAIL, E2E_ADMIN_PASS, E2E_DB_USER, E2E_DB_NAME)', | ||
| ); | ||
|
|
||
| function dbQuery(sql) { | ||
| const args = []; | ||
| if (DB_HOST) { | ||
| args.push('-h', DB_HOST); | ||
| if (DB_PORT) args.push('-P', DB_PORT); | ||
| } else if (DB_SOCKET) { | ||
| args.push('-S', DB_SOCKET); | ||
| } | ||
| args.push('-u', DB_USER, DB_NAME, '-N', '-B', '-e', sql); | ||
| return execFileSync('mysql', args, { | ||
| encoding: 'utf-8', timeout: 10000, | ||
| env: { ...process.env, MYSQL_PWD: DB_PASS }, | ||
| }).trim(); | ||
| } | ||
|
|
||
| // Emulate a real phone (mobile UA + touch) — this is what makes flatpickr | ||
| // consider falling back to the native picker. | ||
| test.use({ ...devices['Pixel 5'] }); | ||
|
|
||
| test.describe('Mobile: book-page loan date picker', () => { | ||
| let bookId = 0; | ||
|
|
||
| test.beforeAll(() => { | ||
| // A book with at least one available copy → the request-loan button shows. | ||
| bookId = parseInt( | ||
| dbQuery( | ||
| "SELECT c.libro_id FROM copie c JOIN libri l ON l.id=c.libro_id " + | ||
| "WHERE c.stato='disponibile' AND l.deleted_at IS NULL LIMIT 1", | ||
| ) || '0', | ||
| 10, | ||
| ); | ||
| }); | ||
|
|
||
| test('uses flatpickr (not the native picker), so availability can be shown', async ({ page }) => { | ||
| test.skip(!bookId, 'No available book to open the loan date picker on'); | ||
|
|
||
| // Login (the request-loan dialog is for authenticated users) | ||
| await page.goto(`${BASE}/accedi`); | ||
| await page.fill('input[name="email"]', ADMIN_EMAIL); | ||
| await page.fill('input[name="password"]', ADMIN_PASS); | ||
| await page.locator('button[type="submit"]').click(); | ||
| await page.waitForURL(/(\/$|admin|profilo|account)/, { timeout: 15000 }).catch(() => {}); | ||
|
|
||
| await page.goto(`${BASE}/libro/${bookId}`); | ||
| const btn = page.locator('#btn-request-loan'); | ||
| test.skip((await btn.count()) === 0, 'This book has no request-loan button'); | ||
|
|
||
| await btn.click(); | ||
| await page.waitForSelector('.swal2-popup', { timeout: 8000 }); | ||
| await page.waitForFunction( | ||
| () => { | ||
| const el = document.querySelector('#swal-date-start'); | ||
| return el && /** @type {any} */ (el)._flatpickr; | ||
| }, | ||
| { timeout: 8000 }, | ||
| ); | ||
|
|
||
| const info = await page.evaluate(() => { | ||
| const el = /** @type {any} */ (document.querySelector('#swal-date-start')); | ||
| const fp = el && el._flatpickr; | ||
| return { | ||
| hasFlatpickr: !!fp, | ||
| disableMobile: fp ? !!fp.config.disableMobile : null, | ||
| // flatpickr sets isMobile=true only when it actually renders the native | ||
| // input; with disableMobile:true it must stay false even on a phone. | ||
| usesNativePicker: fp ? !!fp.isMobile : null, | ||
| nativeDateInput: !!document.querySelector('.swal2-popup input[type="date"]'), | ||
| customCalendar: !!document.querySelector('.flatpickr-calendar'), | ||
| }; | ||
| }); | ||
|
|
||
| expect(info.hasFlatpickr).toBe(true); | ||
| expect(info.disableMobile).toBe(true); | ||
| expect(info.usesNativePicker).toBe(false); // custom calendar, not the OS one | ||
| expect(info.nativeDateInput).toBe(false); // flatpickr did not inject a native <input type=date> | ||
| expect(info.customCalendar).toBe(true); // flatpickr's own calendar is in the DOM | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Non rendere questo spec “false green”.
Il
catch(() => {})suwaitForURLe lotest.skipsul bottone permettono allo spec di passare senza verificare nulla se il login rompe, cambia il redirect o#btn-request-loannon compare. Qui dovrebbe fallire esplicitamente, altrimenti la regressione mobile resta scoperta.Diff proposta
await page.goto(`${BASE}/accedi`); await page.fill('input[name="email"]', ADMIN_EMAIL); await page.fill('input[name="password"]', ADMIN_PASS); await page.locator('button[type="submit"]').click(); - await page.waitForURL(/(\/$|admin|profilo|account)/, { timeout: 15000 }).catch(() => {}); + await page.waitForURL(/(\/$|admin|profilo|account)/, { timeout: 15000 }); await page.goto(`${BASE}/libro/${bookId}`); const btn = page.locator('`#btn-request-loan`'); - test.skip((await btn.count()) === 0, 'This book has no request-loan button'); + await expect(btn).toBeVisible();Based on learnings, negli E2E di questo repo il gating deve evitare skip silenziosi che nascondono il fatto che il flusso non è stato davvero eseguito.
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Learnings