2b31b5e3aa
Refactors the notes feature by removing the `title` field and updating all related tests and UI components to use `content` instead. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7464969c-726a-4ec2-a3b2-8ff63f91f6ed Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw Replit-Helium-Checkpoint-Created: true
126 lines
4.2 KiB
JavaScript
126 lines
4.2 KiB
JavaScript
// Regression for the per-card action row visibility on touch devices.
|
|
// On phones / iPad there is no hover, so the row that holds Send /
|
|
// archive / delete must be visible without any gesture. This test runs
|
|
// in a touch-emulating context, creates a note, and asserts the Send
|
|
// button is visible immediately — no hover required.
|
|
import { test, expect } from "@playwright/test";
|
|
import pg from "pg";
|
|
|
|
const DATABASE_URL = process.env.DATABASE_URL;
|
|
if (!DATABASE_URL) {
|
|
throw new Error("DATABASE_URL must be set to run this UI test");
|
|
}
|
|
|
|
const TEST_PASSWORD = "TestPass123!";
|
|
const TEST_PASSWORD_HASH =
|
|
"$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu";
|
|
|
|
const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
|
const createdUserIds = [];
|
|
|
|
function uniq() {
|
|
return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|
|
|
|
async function createUser(prefix, displayEn) {
|
|
const username = `${prefix}_${uniq()}`;
|
|
const { rows } = await pool.query(
|
|
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
|
|
VALUES ($1, $2, $3, $4, 'en', true) RETURNING id`,
|
|
[username, `${username}@example.com`, TEST_PASSWORD_HASH, displayEn],
|
|
);
|
|
const id = rows[0].id;
|
|
createdUserIds.push(id);
|
|
await pool.query(
|
|
`INSERT INTO user_roles (user_id, role_id)
|
|
SELECT $1, id FROM roles WHERE name = 'user'`,
|
|
[id],
|
|
);
|
|
return { id, username };
|
|
}
|
|
|
|
test.afterAll(async () => {
|
|
if (createdUserIds.length > 0) {
|
|
await pool.query(`DELETE FROM notes WHERE user_id = ANY($1::int[])`, [
|
|
createdUserIds,
|
|
]);
|
|
await pool.query(`DELETE FROM user_roles WHERE user_id = ANY($1::int[])`, [
|
|
createdUserIds,
|
|
]);
|
|
await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [
|
|
createdUserIds,
|
|
]);
|
|
}
|
|
await pool.end();
|
|
});
|
|
|
|
async function loginViaUi(page, username) {
|
|
await page.goto("/login");
|
|
await page.locator("#username").fill(username);
|
|
await page.locator("#password").fill(TEST_PASSWORD);
|
|
await Promise.all([
|
|
page.waitForURL((url) => !url.pathname.endsWith("/login"), {
|
|
timeout: 15_000,
|
|
}),
|
|
page.locator('form button[type="submit"]').click(),
|
|
]);
|
|
}
|
|
|
|
// Emulate a touch device on the default (chromium) browser. We avoid
|
|
// devices["iPad …"] because that pins the browser to webkit, which
|
|
// isn't installed in this environment. Chromium honours hasTouch +
|
|
// no mouse so `(hover: none)` matches.
|
|
test.use({
|
|
hasTouch: true,
|
|
isMobile: true,
|
|
viewport: { width: 820, height: 1180 },
|
|
});
|
|
|
|
test.describe("note card actions on touch devices", () => {
|
|
test("Send button is visible without hovering on touch devices", async ({
|
|
page,
|
|
}) => {
|
|
const user = await createUser("notes_touch_actions", "Touch Tester");
|
|
await loginViaUi(page, user.username);
|
|
|
|
await page.goto("/notes");
|
|
await expect(page.getByTestId("notes-page")).toBeVisible();
|
|
|
|
// Sanity: the touch CSS variant is what we depend on. If the test
|
|
// browser doesn't actually report `(hover: none)`, the assertion
|
|
// below would still hold for the wrong reason (no hover applied),
|
|
// so guard it.
|
|
const noHover = await page.evaluate(
|
|
() => window.matchMedia("(hover: none)").matches,
|
|
);
|
|
expect(noHover).toBe(true);
|
|
|
|
const content = `touch-test ${uniq()}`;
|
|
await page.getByTestId("notes-composer").click();
|
|
await page.getByTestId("notes-composer-content").fill(content);
|
|
const savePromise = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname === "/api/notes" &&
|
|
r.request().method() === "POST" &&
|
|
r.status() >= 200 &&
|
|
r.status() < 300,
|
|
);
|
|
// Tap outside the composer to trigger auto-save.
|
|
await page.touchscreen.tap(10, 10);
|
|
const note = await (await savePromise).json();
|
|
|
|
const card = page.getByTestId(`note-card-${note.id}`);
|
|
await expect(card).toBeVisible();
|
|
|
|
// The Send button must be visible without any hover gesture.
|
|
const sendBtn = page.getByTestId(`note-card-send-${note.id}`);
|
|
await expect(sendBtn).toBeVisible();
|
|
|
|
// And it must actually be clickable (not occluded / opacity-0).
|
|
const opacity = await sendBtn.evaluate(
|
|
(el) => getComputedStyle(el.parentElement).opacity,
|
|
);
|
|
expect(Number(opacity)).toBe(1);
|
|
});
|
|
});
|