From b0630a25e117c176c12654465c7cca6d9ea51e65 Mon Sep 17 00:00:00 2001 From: Robert Perce Date: Sat, 14 Feb 2026 13:35:59 -0600 Subject: [PATCH] wip: more tests --- e2e/custom-expects.ts | 37 ++++++++++++++++++++++ e2e/pages/contact.spec.ts | 64 +++++++++++++++++++++++++++++++++++++++ e2e/pages/home.spec.ts | 10 ++---- e2e/playwright.config.ts | 1 + src/web/contact/mod.rs | 9 ++++-- src/web/mod.rs | 1 + static/contact.css | 2 +- 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 e2e/custom-expects.ts create mode 100644 e2e/pages/contact.spec.ts diff --git a/e2e/custom-expects.ts b/e2e/custom-expects.ts new file mode 100644 index 0000000..9b169e0 --- /dev/null +++ b/e2e/custom-expects.ts @@ -0,0 +1,37 @@ +import { expect, type Locator } from '@playwright/test'; +expect.extend({ + async toBeAbove(self: Locator, other: Locator) { + const name = 'toBeAbove'; + let pass: boolean; + let matcherResult: any; + let selfY: number | null = null; + let otherY: number | null = null; + try { + selfY = (await self.boundingBox())?.y ?? null; + otherY = (await self.boundingBox())?.y ?? null; + pass = selfY !== null && otherY !== null && (selfY < otherY); + } catch (e: any) { + matcherResult = e.matcherResult; + pass = false; + } + + if (this.isNot) { + pass =!pass; + } + + const message = () => this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot }) + + '\n\n' + + `Locator: ${self}\n` + + `Expected: above ${other} (y=${this.utils.printExpected(otherY)})\n` + + (matcherResult ? `Received: y=${this.utils.printReceived(selfY)}` : ''); + + return { + message, + pass, + name, + expected: (this.isNot ? '>=' : '<') + otherY, + actual: selfY, + }; + + } +}); diff --git a/e2e/pages/contact.spec.ts b/e2e/pages/contact.spec.ts new file mode 100644 index 0000000..25f0798 --- /dev/null +++ b/e2e/pages/contact.spec.ts @@ -0,0 +1,64 @@ +import { test, expect } from '@playwright/test'; +import { login, verifyCreateUser, todate } from './util'; + +test.beforeEach(async ({ page }) => { + await login(page); + await verifyCreateUser(page, { names: ['Test Testerson'] }); + await expect(page.locator('#alpine-loaded')).not.toHaveAttribute('x-cloak'); +}); + +test('manual-freshen date is editable', async ({ page }) => { + await page.getByRole('link', { name: /edit/i }).click(); + await expect(page.getByRole('textbox', { name: /freshened/i })).toBeVisible(); +}); + +test('last-contact date on display resolves journal mentions and manual-freshen', async ({ page }) => { + const today = new Date().toISOString().split("T")[0]; + const todayRe = new RegExp(today.substring(0, today.length - 1) + "."); + const entryDate = page.getByPlaceholder(todayRe); + const entryBox = page.getByPlaceholder(/new entry/i); + await entryDate.fill("2025-05-05"); + await entryBox.fill("[[Test Testerson]]"); + await page.getByRole('button', { name: /add entry/i }).click(); + await page.reload(); + await expect(page.locator('#fields')).toContainText("freshened2025-05-05"); +}); + +test.skip("groups wrap nicely", async ({ page }) => { + await page.getByRole('link', { name: /edit/i }).click(); + await expect(page.locator('#alpine-loaded')).not.toHaveAttribute('x-cloak'); + + const groupBox = page.getByPlaceholder(/group name/i); + await groupBox.fill('this is a long group name'); + await page.getByRole('button', { name: /save/i }).click(); + await expect(page.locator('#alpine-loaded')).not.toHaveAttribute('x-cloak'); + + // TODO: this drives to the right location but i can't figure out how to assert + // that the text is all on one line. Manual inspection looks good at time of writing. +}); + +test('allow marking as hidden', async ({ page }) => { + +}); + +test('allow exempting from stale', async ({ page }) => { + +}); + +test('something is fucky with lives_with insertion triggering mention generation', async ({ page }) => { + +}); + +test('bullet points in free text display well', async ({ page }) => { + +}); + +/* +home: contact list scrolls in screen, not off screen +home: clicking off contact list closes it +home: contact list is sorted ignoring case +home: contact list should scroll to current contact in center of view +journal: saving journal entry should stay in edit +journal: sometimes editing entries fucks up mentions (probably another $1/$2 error) +journal: bullet points don't display +*/ diff --git a/e2e/pages/home.spec.ts b/e2e/pages/home.spec.ts index 7d70bbb..13b9fa1 100644 --- a/e2e/pages/home.spec.ts +++ b/e2e/pages/home.spec.ts @@ -1,28 +1,26 @@ import { test, expect } from '@playwright/test'; import { login, verifyCreateUser, todate } from './util'; -test('can log out', async ({ page }) => { +test.beforeEach(async ({ page }) => { await login(page); +}); +test('can log out', async ({ page }) => { await page.getByText("Logout").click(); await expect(page.getByLabel("Username")).toBeVisible(); }); test('has no contacts', async ({ page }) => { - await login(page); - await expect(page.getByRole("navigation").getByRole("link")).toHaveCount(0); }); test('can add contacts', async ({ page }) => { - await login(page); await verifyCreateUser(page, { names: ['John Contact'] }); await verifyCreateUser(page, { names: ['Jack Contact'] }); await expect(page.getByRole("navigation").getByRole("link")).toHaveCount(2); }); test('shows "never" for unfreshened contacts', async ({ page }) => { - await login(page); await verifyCreateUser(page, { names: ['John Contact'] }); await page.getByRole('link', { name: 'Mascarpone' }).click(); @@ -30,7 +28,6 @@ test('shows "never" for unfreshened contacts', async ({ page }) => { }); test('shows the date for fresh contacts', async ({ page }) => { - await login(page); await verifyCreateUser(page, { names: ['John Contact'] }); await page.getByRole('link', { name: /edit/i }).click(); await page.getByRole('button', { name: /fresh/i }).click(); @@ -40,7 +37,6 @@ test('shows the date for fresh contacts', async ({ page }) => { }); test('sidebar is sorted alphabetically', async ({ page }) => { - await login(page); await verifyCreateUser(page, { names: ['Zulu'] }); await verifyCreateUser(page, { names: ['Alfa'] }); await verifyCreateUser(page, { names: ['Golf'] }); diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index f1295d4..1065269 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -1,4 +1,5 @@ import { defineConfig, devices } from '@playwright/test'; +import 'custom-expects'; // purposefully not using ??: we want to replace empty empty string with default const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; diff --git a/src/web/contact/mod.rs b/src/web/contact/mod.rs index bdeac88..525090c 100644 --- a/src/web/contact/mod.rs +++ b/src/web/contact/mod.rs @@ -87,6 +87,11 @@ mod get { .fetch_all(pool) .await?; + let freshened = std::cmp::max( + contact.manually_freshened_at.map(|when| when.date_naive()), + entries.get(0).map(|entry| entry.date), + ); + let phone_numbers: Vec = sqlx::query_as!( PhoneNumber, "select * from phone_numbers where contact_id = $1", @@ -141,8 +146,8 @@ mod get { } label { "freshened" } div { - @if let Some(when) = &contact.manually_freshened_at { - (when.date_naive().to_string()) + @if let Some(freshened) = freshened { + (freshened.to_string()) } @else { "(never)" } diff --git a/src/web/mod.rs b/src/web/mod.rs index 66ab59c..bfee974 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -102,6 +102,7 @@ impl Layout { (content) } } + template #alpine-loaded x-cloak {} } } } diff --git a/static/contact.css b/static/contact.css index d725284..765ff62 100644 --- a/static/contact.css +++ b/static/contact.css @@ -47,7 +47,7 @@ main { #groups { display: flex; flex-direction: column; - width: min-content; + width: fit-content; } #text_body {