feat(landlord-profile): enhance profile completion dialog and form layout
This commit is contained in:
1 parent
12316b8604
commit
62d0f171f4
6 files changed
+81
-11
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
import { expect, Page, test } from '@playwright/test';
|
import { expect, Locator, Page, test } from '@playwright/test';
|
||||||
|
|
||||||
async function mockUser(page: Page, authenticated: boolean, completeProfile = true): Promise<void> {
|
async function mockUser(page: Page, authenticated: boolean, completeProfile = true): Promise<void> {
|
||||||
await page.unroute('**/api/user');
|
await page.unroute('**/api/user');
|
||||||
@@ -43,6 +43,27 @@ async function expectNoHorizontalOverflow(page: Page): Promise<void> {
|
|||||||
expect(hasHorizontalOverflow).toBe(false);
|
expect(hasHorizontalOverflow).toBe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function expectNoScrollbars(locator: Locator): Promise<void> {
|
||||||
|
const scrollableElements = await locator.evaluate((element) => {
|
||||||
|
return [element, ...element.querySelectorAll<HTMLElement>('*')]
|
||||||
|
.filter((candidate) => {
|
||||||
|
const { overflowX, overflowY } = getComputedStyle(candidate);
|
||||||
|
const hasHorizontalScrollbar =
|
||||||
|
(overflowX === 'auto' || overflowX === 'scroll') &&
|
||||||
|
candidate.scrollWidth > candidate.clientWidth;
|
||||||
|
const hasVerticalScrollbar =
|
||||||
|
(overflowY === 'auto' || overflowY === 'scroll') &&
|
||||||
|
candidate.scrollHeight > candidate.clientHeight;
|
||||||
|
return hasHorizontalScrollbar || hasVerticalScrollbar;
|
||||||
|
})
|
||||||
|
.map(
|
||||||
|
(candidate) =>
|
||||||
|
`${candidate.tagName.toLowerCase()}.${candidate.getAttribute('class') ?? ''}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(scrollableElements).toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await mockUser(page, false);
|
await mockUser(page, false);
|
||||||
});
|
});
|
||||||
@@ -74,13 +95,34 @@ test('requires a newly connected landlord to complete their profile', async ({ p
|
|||||||
|
|
||||||
const dialog = page.getByRole('dialog');
|
const dialog = page.getByRole('dialog');
|
||||||
await expect(dialog.getByRole('heading', { name: 'Complétez votre profil' })).toBeVisible();
|
await expect(dialog.getByRole('heading', { name: 'Complétez votre profil' })).toBeVisible();
|
||||||
|
await expectNoHorizontalOverflow(page);
|
||||||
|
await expectNoScrollbars(dialog);
|
||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
await expect(dialog).toBeVisible();
|
await expect(dialog).toBeVisible();
|
||||||
await expect(dialog.getByRole('button', { name: 'Déconnexion' })).toBeVisible();
|
const logoutButton = dialog.getByRole('button', { name: 'Déconnexion' });
|
||||||
|
const saveButton = dialog.getByRole('button', { name: 'Enregistrer le profil' });
|
||||||
|
await expect(logoutButton).toBeVisible();
|
||||||
|
await expect(saveButton).toBeVisible();
|
||||||
|
|
||||||
|
const [logoutBox, saveBox, actionBox, surfaceBox] = await Promise.all([
|
||||||
|
logoutButton.boundingBox(),
|
||||||
|
saveButton.boundingBox(),
|
||||||
|
dialog.locator('.profile-actions').boundingBox(),
|
||||||
|
dialog.locator('.mat-mdc-dialog-surface').boundingBox(),
|
||||||
|
]);
|
||||||
|
if (!logoutBox || !saveBox || !actionBox || !surfaceBox) {
|
||||||
|
throw new Error('Profile dialog actions must be measurable.');
|
||||||
|
}
|
||||||
|
expect(logoutBox.x).toBeCloseTo(actionBox.x, 1);
|
||||||
|
expect(saveBox.x + saveBox.width).toBeCloseTo(actionBox.x + actionBox.width, 1);
|
||||||
|
expect(Math.abs(logoutBox.y - saveBox.y)).toBeLessThan(8);
|
||||||
|
expect(surfaceBox.y + surfaceBox.height - (actionBox.y + actionBox.height)).toBeGreaterThanOrEqual(
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
|
||||||
await dialog.getByLabel('Téléphone').fill('+33612345678');
|
await dialog.getByLabel('Téléphone').fill('+33612345678');
|
||||||
await dialog.getByLabel('Adresse postale').fill('1 rue des Lilas, Lyon');
|
await dialog.getByLabel('Adresse postale').fill('1 rue des Lilas, Lyon');
|
||||||
await dialog.getByRole('button', { name: 'Enregistrer le profil' }).click();
|
await saveButton.click();
|
||||||
|
|
||||||
await expect(dialog).toBeHidden();
|
await expect(dialog).toBeHidden();
|
||||||
await expect(page.getByTestId('primary-navigation')).toBeVisible();
|
await expect(page.getByTestId('primary-navigation')).toBeVisible();
|
||||||
|
|||||||
+2
-3
@@ -3,9 +3,8 @@
|
|||||||
width: min(36rem, calc(100vw - 3rem));
|
width: min(36rem, calc(100vw - 3rem));
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
gap: var(--space-4, 1rem);
|
gap: var(--space-4, 1rem);
|
||||||
max-height: 75vh;
|
padding-inline: var(--space-6, 1.5rem);
|
||||||
overscroll-behavior-y: contain;
|
padding-block-end: var(--space-6, 1.5rem);
|
||||||
container-type: inline-size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-dialog-content > p,
|
.profile-dialog-content > p,
|
||||||
|
|||||||
+18
-5
@@ -1,5 +1,5 @@
|
|||||||
<h2 mat-dialog-title>Complétez votre profil</h2>
|
<h2 mat-dialog-title>Complétez votre profil</h2>
|
||||||
<mat-dialog-content class="profile-dialog-content">
|
<div class="profile-dialog-content">
|
||||||
<p>Vos coordonnées sont nécessaires pour créer vos baux et vos quittances.</p>
|
<p>Vos coordonnées sont nécessaires pour créer vos baux et vos quittances.</p>
|
||||||
|
|
||||||
@if (profileStatus() === 'loading') {
|
@if (profileStatus() === 'loading') {
|
||||||
@@ -13,13 +13,26 @@
|
|||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<app-landlord-profile-form
|
<app-landlord-profile-form
|
||||||
|
class="profile-completion-form"
|
||||||
[profile]="profile()"
|
[profile]="profile()"
|
||||||
[saving]="saving()"
|
[saving]="saving()"
|
||||||
[saveError]="error()"
|
[saveError]="error()"
|
||||||
(saveRequested)="save($event)"
|
(saveRequested)="save($event)"
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
profile-form-secondary-action
|
||||||
|
mat-button
|
||||||
|
type="button"
|
||||||
|
(click)="logout()"
|
||||||
|
[disabled]="saving()"
|
||||||
|
>
|
||||||
|
Déconnexion
|
||||||
|
</button>
|
||||||
|
</app-landlord-profile-form>
|
||||||
}
|
}
|
||||||
</mat-dialog-content>
|
</div>
|
||||||
<mat-dialog-actions align="end">
|
@if (profileStatus() !== 'ready') {
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
<button mat-button type="button" (click)="logout()" [disabled]="saving()">Déconnexion</button>
|
<button mat-button type="button" (click)="logout()" [disabled]="saving()">Déconnexion</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
container-type: inline-size;
|
container-type: inline-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host(.profile-completion-form) {
|
||||||
|
--mat-button-text-horizontal-padding: var(--space-1, 0.25rem);
|
||||||
|
--mat-button-filled-horizontal-padding: var(--space-1, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
.profile-form {
|
.profile-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-4, 1rem);
|
gap: var(--space-4, 1rem);
|
||||||
@@ -32,6 +37,12 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host(.profile-completion-form) .profile-actions {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-1, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
@container (min-width: 32rem) {
|
@container (min-width: 32rem) {
|
||||||
.profile-fields {
|
.profile-fields {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
/></mat-form-field>
|
/></mat-form-field>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="profile-actions">
|
<div class="profile-actions">
|
||||||
|
<ng-content select="[profile-form-secondary-action]" />
|
||||||
<button mat-flat-button type="submit" class="primary-action">
|
<button mat-flat-button type="submit" class="primary-action">
|
||||||
@if (saving()) {
|
@if (saving()) {
|
||||||
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
@include mat.core();
|
@include mat.core();
|
||||||
|
|
||||||
|
.mat-mdc-dialog-surface:has(app-landlord-profile-completion-dialog) {
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
html {
|
html {
|
||||||
@include mat.theme(
|
@include mat.theme(
|
||||||
|
|||||||
Reference in new issue
Block a user