/** * @fileoverview Tests for the new activity/offers system in TimeSafari * * CRITICAL UNDERSTANDING: Offer Acknowledgment System * =================================================== * * This file tests the offer acknowledgment mechanism, which was clarified through * systematic debugging investigation. Key findings: * * 1. POINTER-BASED TRACKING: * - TimeSafari uses `lastAckedOfferToUserJwtId` to track the last acknowledged offer * - Offers with IDs newer than this pointer are considered "new" and counted * - The UI shows count of offers newer than the pointer * * 2. TWO DISMISSAL MECHANISMS: * a) COMPLETE DISMISSAL (used in this test): * - Triggered by: Expanding offers section (clicking chevron) * - Method: expandOffersToUserAndMarkRead() * - Action: Sets lastAckedOfferToUserJwtId = newOffersToUser[0].jwtId (newest) * - Result: ALL offers marked as read, count becomes 0 (hidden) * * b) SELECTIVE DISMISSAL: * - Triggered by: Clicking "Keep all above as new offers" * - Method: markOffersAsReadStartingWith(jwtId) * - Action: Sets lastAckedOfferToUserJwtId = nextOffer.jwtId (partial) * - Result: Only offers above clicked offer marked as read * * 3. BROWSER COMPATIBILITY: * - Initially appeared to be Chromium-specific issue * - Investigation revealed test logic error, not browser incompatibility * - Both Chromium and Firefox now pass consistently * * @author Matthew Raymer * @since Investigation completed 2024-12-27 */ import { test, expect } from '@playwright/test'; import { importUser, generateNewEthrUser, switchToUser } from './testUtils'; test('New offers for another user', async ({ page }) => { const user01Did = await generateNewEthrUser(page); await page.goto('./'); await page.getByTestId('closeOnboardingAndFinish').click(); await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden(); await importUser(page, '00'); await page.goto('./contacts'); await page.getByPlaceholder('URL or DID, Name, Public Key').fill(user01Did + ', A Friend'); await expect(page.locator('button > svg.fa-plus')).toBeVisible(); await page.locator('button > svg.fa-plus').click(); await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible(); await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone // show buttons to make offers directly to people await page.getByRole('button').filter({ hasText: /See Actions/i }).click(); // make an offer directly to user 1 // Generate a random string of 3 characters, skipping the "0." at the beginning const randomString1 = Math.random().toString(36).substring(2, 5); await page.getByTestId('offerButton').click(); await page.getByTestId('inputDescription').fill(`help of ${randomString1} from #000`); await page.getByTestId('inputOfferAmount').fill('1'); await page.getByRole('button', { name: 'Sign & Send' }).click(); await expect(page.getByText('That offer was recorded.')).toBeVisible(); await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone // make another offer to user 1 const randomString2 = Math.random().toString(36).substring(2, 5); await page.getByTestId('offerButton').click(); await page.getByTestId('inputDescription').fill(`help of ${randomString2} from #000`); await page.getByTestId('inputOfferAmount').fill('3'); await page.getByRole('button', { name: 'Sign & Send' }).click(); await expect(page.getByText('That offer was recorded.')).toBeVisible(); await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone // as user 1, go to the home page and check that two offers are shown as new await switchToUser(page, user01Did); await page.goto('./'); await page.waitForLoadState('networkidle'); let offerNumElemForTest = page.getByTestId('newDirectOffersActivityNumber'); await expect(offerNumElemForTest).toHaveText('2'); // click on the number of new offers to go to the list page await offerNumElemForTest.click(); await expect(page.getByText('New Offers To You', { exact: true })).toBeVisible(); await page.getByTestId('showOffersToUser').locator('div > svg.fa-chevron-right').click(); // note that they show in reverse chronologicalorder await expect(page.getByText(`help of ${randomString2} from #000`)).toBeVisible(); await expect(page.getByText(`help of ${randomString1} from #000`)).toBeVisible(); /** * OFFER ACKNOWLEDGMENT MECHANISM: * * TimeSafari uses a pointer-based system to track which offers are "new": * - `lastAckedOfferToUserJwtId` stores the ID of the last acknowledged offer * - Offers newer than this pointer are considered "new" and counted * * Two dismissal mechanisms exist: * 1. COMPLETE DISMISSAL: Expanding the offers section calls expandOffersToUserAndMarkRead() * which sets lastAckedOfferToUserJwtId = newOffersToUser[0].jwtId (newest offer) * Result: ALL offers marked as read, count goes to 0 * * 2. SELECTIVE DISMISSAL: "Keep all above" calls markOffersAsReadStartingWith(jwtId) * which sets lastAckedOfferToUserJwtId = nextOffer.jwtId (partial dismissal) * Result: Only offers above the clicked offer are marked as read * * This test uses mechanism #1 (expansion) for complete dismissal. * The expansion already happened when we clicked the chevron above. */ // now see that all offers are dismissed since we expanded the section await page.goto('./'); await page.waitForLoadState('networkidle'); // wait until the list with ID listLatestActivity has at least one visible item await page.locator('#listLatestActivity li').first().waitFor({ state: 'visible' }); await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden(); });