Pomoc z rysunkami ekranow, kafelki na cala szerokosc, kalkulator na danych GUS i Eurostatu
Deploy Polska Lokalnie / deploy (push) Successful in 22s

Pomoc i kontakt:
- kanaly kontaktu wypelniaja szerokosc panelu (auto-fit zamiast czterech kolumn)
- FAQ podzielone na cztery moduly, 21 pytan, ze schematycznymi rysunkami ekranow
  (inline SVG w helpIllustrations.tsx - nie zrzuty, wiec nie dezaktualizuja sie)
- menu konta nie przykleja sie juz nad trescia na telefonie i tablecie

Kafelki wyboru:
- kolumna nie schodzi ponizej czytelnej szerokosci, podpisy lamia sie miedzy slowami
  zamiast po literach (bylo widoczne na monitorach ok. 1440 px)

Kalkulator zdolnosci kredytowej:
- rodzaj oprocentowania decyduje o buforze: 2,5 p.p. przy stalym, 5 p.p. przy zmiennym
  (wczesniej zawsze 2,5 p.p., co zawyzalo zdolnosc przy zmiennej stopie o ok. 100 tys. zl)
- nowy modul market: przecietne wynagrodzenie z GUS BDL i stopa rynku pienieznego 3M
  z Eurostatu, odswiezane co tydzien, publiczny GET /api/market-indicators
- prog wyzszego DStI i podpowiadane oprocentowanie licza sie z tych wskaznikow,
  z podpisem zrodla i okresu; brak danych = wartosci zapasowe

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 12:13:10 +02:00
parent cf02f480ed
commit d9819d0488
13 changed files with 1494 additions and 60 deletions
+160
View File
@@ -853,3 +853,163 @@ test.describe('Ustawienia powiadomien', () => {
await page.waitForTimeout(700);
});
});
// Pomoc i kontakt: kanaly kontaktu wypelniaja szerokosc panelu, a FAQ jest podzielone
// na moduly serwisu i pokazuje schematyczne rysunki ekranow.
test.describe('Pomoc i kontakt', () => {
test('kanaly kontaktu zajmuja cala szerokosc panelu', async ({ page }) => {
await login(page);
await goto(page, ROUTES.accountHelpContact);
const grid = await page.locator('.help-contact-channel-grid').boundingBox();
const cards = await page.locator('.help-contact-channel-card').all();
expect(cards.length).toBeGreaterThan(0);
const last = await cards[cards.length - 1].boundingBox();
// Ostatnia karta konczy sie na prawej krawedzi siatki - zadnej pustej kolumny.
expect(grid!.x + grid!.width - (last!.x + last!.width)).toBeLessThanOrEqual(2);
});
test('FAQ ma grupy modulow i rysunek w odpowiedzi', async ({ page }) => {
await login(page);
await goto(page, ROUTES.accountHelpContact);
const groups = page.locator('.help-faq-group');
await expect(groups).toHaveCount(4);
await expect(page.locator('.help-contact-faq-list details').first()).toBeVisible();
const item = page.locator('.help-contact-faq-list details').filter({ hasText: 'Jak dodać ogłoszenie?' }).first();
await expect(item.locator('.help-figure')).toBeHidden();
await item.locator('summary').click();
await expect(item.locator('.help-figure svg')).toBeVisible();
await expect(item.locator('figcaption')).not.toBeEmpty();
});
});
// Kafelki wyboru nie moga sciskac sie do kilkudziesieciu pikseli - wtedy podpisy lamaly sie
// po literach ("Og rze wa nie po dlo go we"). Kolumna ma minimalna szerokosc, reszta idzie nizej.
test.describe('Kafelki wyboru w kreatorze wyceny', () => {
for (const width of [1920, 1600, 1440, 1366, 1280, 1100]) {
test(`podpisy udogodnien mieszcza sie przy szerokosci ${width}`, async ({ page }) => {
await page.setViewportSize({ width, height: 1000 });
await goto(page, ROUTES.valuationCondition);
const labels = page.locator('.valuation-amenity-grid button span');
await expect(labels.first()).toBeVisible();
const measured = await labels.evaluateAll((nodes) => nodes.map((node) => {
const rect = node.getBoundingClientRect();
return { text: node.textContent ?? '', width: rect.width, height: rect.height };
}));
for (const item of measured) {
// Ponizej ~90 px nawet krotkie slowo zaczyna sie lamac w pionie.
expect(item.width, `podpis "${item.text}"`).toBeGreaterThan(90);
// Najdluzsza nazwa to dwa slowa - wiecej niz dwa wiersze znacza lamanie po literach.
expect(item.height, `podpis "${item.text}"`).toBeLessThanOrEqual(34);
}
});
}
});
// Ten sam problem co w kreatorze wyceny dotyczyl kafelkow mediow i udogodnien w formularzu
// ogloszenia. Test przechodzi kreator do kroku "Dodatkowe informacje" i mierzy podpisy.
test.describe('Kafelki wyboru w formularzu ogloszenia', () => {
test('podpisy mediow i udogodnien lamia sie miedzy slowami', async ({ page }) => {
// Formularz edycji wchodzi z kompletem danych, wiec kroki przepuszczaja walidacje.
// Konto z gotowymi ogloszeniami: to samo, ktorego uzywaja testy ulubionych i wyszukiwan.
await goto(page, ROUTES.login);
await page.getByPlaceholder('Wpisz swój adres e-mail').fill('anna.kowalska@example.com');
await page.getByPlaceholder('Wpisz swoje hasło').fill(ADMIN.password);
await page.getByRole('button', { name: 'Zaloguj się', exact: true }).click();
await page.waitForURL(/\/konto$/);
const token = await page.evaluate(() => window.localStorage.getItem('polskalokalnie-auth-token'));
const own = await page.request.get('/api/listings/mine', { headers: { Authorization: `Bearer ${token}` } })
.then((response) => response.json());
expect(own.length, 'konto testowe musi mieć własne ogłoszenie').toBeGreaterThan(0);
await page.setViewportSize({ width: 1440, height: 1200 });
await goto(page, `/edytuj-ogloszenie/${own[0].id}`);
// Baner zgody stoi nad paskiem akcji na dole kroku - bez zamkniecia przejmuje klikniecie.
const consent = page.getByRole('button', { name: 'Tylko niezbędne' });
if (await consent.count() > 0) {
await consent.click();
}
// Formularz edycji dociaga dane ogloszenia - dopoki pola sa puste, walidacja nie puszcza dalej.
await expect(page.locator('.add-steps-item.active strong')).toContainText('Szczegóły oferty');
await page.waitForTimeout(1500);
// Trzy kroki dalej: Szczegoly -> Lokalizacja -> Zdjecia -> Dodatkowe informacje.
for (const next of ['Lokalizacja', 'Zdjęcia', 'Dodatkowe informacje']) {
await page.getByRole('button', { name: new RegExp('^Dalej') }).first().click();
await expect(page.locator('.add-steps-item.active strong')).toContainText(next);
}
await expect(page.locator('.add-steps-item.active strong')).toContainText('Dodatkowe informacje');
const labels = page.locator('.extra-option-grid label span');
await expect(labels.first()).toBeVisible();
const measured = await labels.evaluateAll((nodes) => nodes.map((node) => ({
text: node.textContent ?? '',
height: node.getBoundingClientRect().height,
})));
expect(measured.length).toBeGreaterThan(5);
for (const item of measured) {
// Najdluzsze podpisy to trzy slowa - powyzej dwoch wierszy znaczy lamanie po literach.
expect(item.height, `podpis "${item.text}"`).toBeLessThanOrEqual(34);
}
});
});
// Kalkulator zdolnosci: bufor zalezy od rodzaju oprocentowania (Rekomendacja S), a prog wyzszego
// DStI i podpowiadane oprocentowanie pochodza ze wskaznikow pobieranych z GUS i Eurostatu.
test.describe('Kalkulator zdolnosci kredytowej', () => {
test('wskazniki rynkowe sa dostepne publicznie', async ({ page }) => {
const indicators = await page.request.get('/api/market-indicators').then((response) => response.json());
const keys = indicators.map((item: { key: string }) => item.key);
expect(keys).toContain('average-wage-gross');
expect(keys).toContain('money-market-3m');
for (const item of indicators) {
expect(item.value, `wskaznik ${item.key}`).toBeGreaterThan(0);
expect(item.asOf, `wskaznik ${item.key}`).toBeTruthy();
expect(item.source, `wskaznik ${item.key}`).toBeTruthy();
}
});
test('zmienne oprocentowanie daje nizsza zdolnosc niz stale', async ({ page }) => {
await goto(page, ROUTES.creditCalculator);
await page.locator('.credit-field').filter({ hasText: 'Twój dochód' }).locator('input').fill('9000');
await page.waitForTimeout(400);
const maxLoan = async () => {
const row = page.locator('.credit-details article, .credit-detail-row, .credit-detail')
.filter({ hasText: 'Maksymalna kwota kredytu' }).first();
const text = await row.innerText();
return Number(text.replace(/[^\d]/g, ''));
};
await page.getByRole('button', { name: 'Stałe', exact: true }).click();
await page.waitForTimeout(400);
const withFixed = await maxLoan();
await page.getByRole('button', { name: 'Zmienne', exact: true }).click();
await page.waitForTimeout(400);
const withVariable = await maxLoan();
expect(withFixed).toBeGreaterThan(0);
// Wyzszy bufor przy zmiennej stopie musi obnizyc maksymalna kwote kredytu.
expect(withVariable).toBeLessThan(withFixed);
});
test('podpowiadane oprocentowanie i prog DStI podaja zrodlo', async ({ page }) => {
await goto(page, ROUTES.creditCalculator);
await page.waitForTimeout(800);
const rate = await page.locator('.credit-field').filter({ hasText: 'Oprocentowanie' }).locator('input').inputValue();
expect(Number(rate.replace(',', '.'))).toBeGreaterThan(0);
const notes = (await page.locator('.credit-method-note').allInnerTexts()).join(' ');
expect(notes).toContain('GUS');
expect(notes).toContain('Eurostat');
await expect(page.locator('.credit-details, .credit-detail-row, .credit-detail').first()).toContainText('próg');
});
});