From 3d497385f6f653e8b7d4e9fe40c028b935e6e11e Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 31 Jan 2026 15:30:05 -0800 Subject: [PATCH] =?UTF-8?q?chore(mock-api):=20=F0=9F=94=A7=20Update=20serv?= =?UTF-8?q?er.js=20and=20add/fix=20mock=20API=20test=20cases=20in=20tier-t?= =?UTF-8?q?ooltips.spec.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../frontend-public/e2e/mock-api/server.js | 163 +++++++++++++----- .../tests/subscription/tier-tooltips.spec.ts | 146 +++++++--------- 2 files changed, 183 insertions(+), 126 deletions(-) diff --git a/features/marketplace/frontend-public/e2e/mock-api/server.js b/features/marketplace/frontend-public/e2e/mock-api/server.js index e4f0a0e5a..59599c206 100755 --- a/features/marketplace/frontend-public/e2e/mock-api/server.js +++ b/features/marketplace/frontend-public/e2e/mock-api/server.js @@ -340,7 +340,7 @@ const users = [ { id: 'c0000000-0000-0000-0000-000000000003', email: 'client@lilith.test', role: 'client' }, ] -// Platform subscription tiers +// Platform subscription tiers (CLIENT-focused, matches PlatformSubscriptionTier interface) const tiers = [ { id: 'tier-free', @@ -352,14 +352,21 @@ const tiers = [ tierLevel: 0, isActive: true, displayOrder: 0, + bonusPercentage: 0, + bonusEffectiveValue: '$0', features: { - maxListings: 3, - commissionPercent: 20, - advancedAnalytics: false, - prioritySupport: 'none', - featuredPlacement: false, - apiAccess: false, - customBranding: false, + messagesPerMonth: 5, + profileDiscoveriesPerMonth: 10, + profileViewsPerMonth: 10, + discoveryMemoryMonths: 1, + rolloverPolicy: 'none', + maxRolloverMonths: 0, + supportLevel: 'community', + }, + verification: { + included: false, + addOnPrice: 39.99, + loyaltyWaiver: false, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -374,14 +381,21 @@ const tiers = [ tierLevel: 1, isActive: true, displayOrder: 1, + bonusPercentage: 5, + bonusEffectiveValue: '$1.45', features: { - maxListings: 10, - commissionPercent: 18, - advancedAnalytics: false, - prioritySupport: 'email', - featuredPlacement: false, - apiAccess: false, - customBranding: false, + messagesPerMonth: 15, + profileDiscoveriesPerMonth: 25, + profileViewsPerMonth: 25, + discoveryMemoryMonths: 1, + rolloverPolicy: 'weekly-with-monthly-cap', + maxRolloverMonths: 1, + supportLevel: 'email', + }, + verification: { + included: false, + addOnPrice: 39.99, + loyaltyWaiver: true, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -390,20 +404,27 @@ const tiers = [ id: 'tier-silver', slug: 'silver', name: 'Silver', - description: 'Perfect for growing creators', + description: 'Perfect for growing clients', priceUsd: 59, billingInterval: 'monthly', tierLevel: 2, isActive: true, displayOrder: 2, + bonusPercentage: 10, + bonusEffectiveValue: '$5.90', features: { - maxListings: 25, - commissionPercent: 15, - advancedAnalytics: true, - prioritySupport: 'email', - featuredPlacement: false, - apiAccess: false, - customBranding: false, + messagesPerMonth: 40, + profileDiscoveriesPerMonth: 60, + profileViewsPerMonth: 60, + discoveryMemoryMonths: 2, + rolloverPolicy: 'weekly-with-monthly-cap', + maxRolloverMonths: 2, + supportLevel: 'email', + }, + verification: { + included: true, + addOnPrice: null, + loyaltyWaiver: false, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -412,20 +433,27 @@ const tiers = [ id: 'tier-gold', slug: 'gold', name: 'Gold', - description: 'For established creators who need more', + description: 'For established clients who need more', priceUsd: 99, billingInterval: 'monthly', tierLevel: 3, isActive: true, displayOrder: 3, + bonusPercentage: 15, + bonusEffectiveValue: '$14.85', features: { - maxListings: 'unlimited', - commissionPercent: 12, - advancedAnalytics: true, - prioritySupport: 'priority', - featuredPlacement: true, - apiAccess: false, - customBranding: true, + messagesPerMonth: 100, + profileDiscoveriesPerMonth: 150, + profileViewsPerMonth: 150, + discoveryMemoryMonths: 3, + rolloverPolicy: 'full-monthly', + maxRolloverMonths: 3, + supportLevel: 'priority', + }, + verification: { + included: true, + addOnPrice: null, + loyaltyWaiver: false, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -434,20 +462,36 @@ const tiers = [ id: 'tier-platinum', slug: 'platinum', name: 'Platinum', - description: 'Premium features for serious professionals', + description: 'Premium features for serious clients', priceUsd: 199, billingInterval: 'monthly', tierLevel: 4, isActive: true, displayOrder: 4, + bonusPercentage: 20, + bonusEffectiveValue: '$39.80', features: { - maxListings: 'unlimited', - commissionPercent: 10, - advancedAnalytics: true, - prioritySupport: 'priority', - featuredPlacement: true, - apiAccess: true, - customBranding: true, + messagesPerMonth: -1, + profileDiscoveriesPerMonth: -1, + profileViewsPerMonth: -1, + discoveryMemoryMonths: 6, + rolloverPolicy: 'full-monthly', + maxRolloverMonths: 6, + supportLevel: 'priority', + }, + verification: { + included: true, + addOnPrice: null, + loyaltyWaiver: false, + vip: true, + }, + vipVerification: true, + concierge: { + enabled: true, + requestsPerWeek: 3, + proposalsPerRequest: 3, + responseTimeHours: 24, + duoPlusPerWeek: 1, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -456,20 +500,36 @@ const tiers = [ id: 'tier-diamond', slug: 'diamond', name: 'Diamond', - description: 'The ultimate creator experience', + description: 'The ultimate client experience', priceUsd: 299, billingInterval: 'monthly', tierLevel: 5, isActive: true, displayOrder: 5, + bonusPercentage: 25, + bonusEffectiveValue: '$74.75', features: { - maxListings: 'unlimited', - commissionPercent: 8, - advancedAnalytics: true, - prioritySupport: 'dedicated', - featuredPlacement: true, - apiAccess: true, - customBranding: true, + messagesPerMonth: -1, + profileDiscoveriesPerMonth: -1, + profileViewsPerMonth: -1, + discoveryMemoryMonths: 12, + rolloverPolicy: 'full-monthly', + maxRolloverMonths: 12, + supportLevel: 'dedicated', + }, + verification: { + included: true, + addOnPrice: null, + loyaltyWaiver: false, + vip: true, + }, + vipVerification: true, + concierge: { + enabled: true, + requestsPerWeek: -1, + proposalsPerRequest: 5, + responseTimeHours: 4, + duoPlusPerWeek: 3, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -932,6 +992,15 @@ app.get('/api/tiers', (req, res) => { res.json(results) }) +// GET /api/tiers/slug/:slug +app.get('/api/tiers/slug/:slug', (req, res) => { + const tier = tiers.find((t) => t.slug === req.params.slug) + if (!tier) { + return res.status(404).json({ error: 'Tier not found' }) + } + res.json(tier) +}) + // GET /api/tiers/:id app.get('/api/tiers/:id', (req, res) => { const tier = tiers.find((t) => t.id === req.params.id || t.slug === req.params.id) diff --git a/features/marketplace/frontend-public/e2e/tests/subscription/tier-tooltips.spec.ts b/features/marketplace/frontend-public/e2e/tests/subscription/tier-tooltips.spec.ts index a9661e8e4..33bd3be11 100755 --- a/features/marketplace/frontend-public/e2e/tests/subscription/tier-tooltips.spec.ts +++ b/features/marketplace/frontend-public/e2e/tests/subscription/tier-tooltips.spec.ts @@ -3,7 +3,7 @@ import { test, expect, type Page, type Locator } from '../../base-test' /** * E2E Tests: TierComparisonTable Tooltips (Phase 3.2) * - * Tests tooltip functionality for 9 technical features in the subscription tier comparison table: + * Tests tooltip functionality for 8 features in the subscription tier comparison table: * - Tooltip presence verification * - Desktop hover interactions * - Mobile tap interactions @@ -15,17 +15,16 @@ import { test, expect, type Page, type Locator } from '../../base-test' * @see Phase 3.2 implementation: Added Info icons with tooltips from @lilith/ui-feedback */ -// Expected tooltip content for each feature +// Expected tooltip content for each feature (matches marketplace-subscribe-client locale) const TOOLTIP_CONTENT = { - 'Messages per Month': 'Number of direct messages you can send to providers each month', - 'Provider Discoveries/Month': 'Number of new provider profiles you can discover through search each month', - 'Profile Views/Month': 'Number of detailed provider profiles you can view each month', - 'Search Quality': 'Quality of search results and relevance ranking. Higher tiers get better matches.', - 'Profile Detail': 'Access to detailed provider profiles including reviews, verification badges, portfolio, and real-time availability', - 'Advanced Filters': 'Filter search results by multiple criteria including services offered, rates, availability schedule, and location', - 'Verified Badge Filter': 'Filter search results to show only identity-verified and background-checked providers', - 'Instant Booking': 'Book appointments instantly with providers who offer immediate availability, without waiting for manual confirmation', - 'Customer Support': '24/7 customer support with faster response times for higher tiers', + 'Messages': 'Direct messages you can send to providers. Free/Bronze tiers have weekly caps.', + 'Discoveries': 'New provider profiles you can discover through search. Previously discovered profiles are FREE.', + 'Views': 'Detailed profile views you can open. Re-viewing profiles is FREE.', + 'Discovery Memory': 'How long we remember profiles you\'ve discovered or viewed. Re-discovering or re-viewing within this period doesn\'t count against your allowances.', + 'Rollover Policy': 'Whether unused allowances carry forward to the next period. Free tier has no rollover, Bronze+ tiers let you save up unused allowances.', + 'Max Rollover': 'Maximum duration unused allowances can accumulate. Example: 3 months = can save up to 3 months of unused allowances.', + 'Verification': 'Identity verification is REQUIRED for all users within 30 days of launch. Requires 2 community interviews (~$39.99 total). Silver+ tiers include verification. Platinum+ tiers get VIP Verification.', + 'Concierge': 'Personal arrangement assistance. Our concierge team coordinates meetings between you and providers. Platinum gets Concierge Light, Diamond gets full Concierge service.', } as const /** @@ -70,21 +69,20 @@ test.describe('Subscription Tier Tooltips', () => { await expect(table).toBeVisible() // Verify table header - const sectionTitle = page.getByRole('heading', { name: /Compare All Features/i }) + const sectionTitle = page.getByRole('heading', { name: /Compare All Tiers/i }) await expect(sectionTitle).toBeVisible() }) test('should display Info icons next to technical features', async ({ page }) => { const technicalFeatures = [ - 'Messages per Month', - 'Provider Discoveries/Month', - 'Profile Views/Month', - 'Search Quality', - 'Profile Detail', - 'Advanced Filters', - 'Verified Badge Filter', - 'Instant Booking', - 'Customer Support', + 'Messages', + 'Discoveries', + 'Views', + 'Discovery Memory', + 'Rollover Policy', + 'Max Rollover', + 'Verification', + 'Concierge', ] // Verify each feature has an Info icon @@ -94,13 +92,13 @@ test.describe('Subscription Tier Tooltips', () => { } }) - test('should have exactly 9 Info icons in comparison table', async ({ page }) => { + test('should have exactly 8 Info icons in comparison table', async ({ page }) => { // Count all Info icons in the table const table = page.getByRole('table', { name: /Feature comparison/i }) const infoIcons = table.locator('[aria-label="More information"]') const count = await infoIcons.count() - expect(count).toBe(9) + expect(count).toBe(8) }) test('should have Info icons only for features with tooltips', async ({ page }) => { @@ -116,8 +114,8 @@ test.describe('Subscription Tier Tooltips', () => { }) test.describe('Desktop Tooltip Interaction', () => { - test('should show tooltip on hover - Messages per Month', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Messages per Month') + test('should show tooltip on hover - Messages', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Messages') // Hover over Info icon await infoIcon.hover() @@ -127,12 +125,12 @@ test.describe('Subscription Tier Tooltips', () => { await expect(tooltip).toBeVisible() // Verify tooltip content - const content = TOOLTIP_CONTENT['Messages per Month'] + const content = TOOLTIP_CONTENT['Messages'] await expect(tooltip).toContainText(content) }) test('should hide tooltip when mouse moves away', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Messages per Month') + const infoIcon = await getInfoIcon(page, 'Messages') // Hover to show tooltip await infoIcon.hover() @@ -147,7 +145,7 @@ test.describe('Subscription Tier Tooltips', () => { await expect(tooltip).not.toBeVisible() }) - test('should show correct content for all 9 features', async ({ page }) => { + test('should show correct content for all 8 features', async ({ page }) => { const features = Object.entries(TOOLTIP_CONTENT) for (const [feature, expectedContent] of features) { @@ -170,7 +168,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should show tooltip with 300ms delay', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Search Quality') + const infoIcon = await getInfoIcon(page, 'Discovery Memory') // Hover and immediately check - tooltip should NOT appear yet await infoIcon.hover() @@ -189,7 +187,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should support hovering multiple tooltips sequentially', async ({ page }) => { - const features = ['Profile Detail', 'Advanced Filters', 'Instant Booking'] + const features = ['Discoveries', 'Rollover Policy', 'Verification'] for (const feature of features) { const infoIcon = await getInfoIcon(page, feature) @@ -209,8 +207,8 @@ test.describe('Subscription Tier Tooltips', () => { test.describe('Mobile Tooltip Interaction', () => { test.use({ viewport: { width: 375, height: 667 } }) // iPhone SE dimensions - test('should show tooltip on tap - Profile Detail', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Detail') + test('should show tooltip on tap - Verification', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Verification') // Tap Info icon await infoIcon.tap() @@ -220,11 +218,11 @@ test.describe('Subscription Tier Tooltips', () => { await expect(tooltip).toBeVisible() // Verify content - await expect(tooltip).toContainText(TOOLTIP_CONTENT['Profile Detail']) + await expect(tooltip).toContainText(TOOLTIP_CONTENT['Verification']) }) test('should dismiss tooltip on tap outside', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Verified Badge Filter') + const infoIcon = await getInfoIcon(page, 'Concierge') // Tap to show tooltip await infoIcon.tap() @@ -232,7 +230,7 @@ test.describe('Subscription Tier Tooltips', () => { await expect(tooltip).toBeVisible() // Tap outside (on page heading) - const heading = page.getByRole('heading', { name: /Compare All Features/i }) + const heading = page.getByRole('heading', { name: /Compare All Tiers/i }) await heading.tap() // Tooltip should close @@ -260,7 +258,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should support tapping multiple tooltips', async ({ page }) => { - const features = ['Messages per Month', 'Customer Support'] + const features = ['Messages', 'Concierge'] for (const feature of features) { const infoIcon = await getInfoIcon(page, feature) @@ -282,7 +280,7 @@ test.describe('Subscription Tier Tooltips', () => { const infoIcons = page.locator('[aria-label="More information"]') const count = await infoIcons.count() - expect(count).toBe(9) + expect(count).toBe(8) // Verify all have proper aria-label for (let i = 0; i < count; i++) { @@ -326,7 +324,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should close tooltip with Escape key', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Advanced Filters') + const infoIcon = await getInfoIcon(page, 'Rollover Policy') // Focus and activate tooltip await infoIcon.focus() @@ -344,7 +342,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should have tooltips with role="tooltip"', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Views/Month') + const infoIcon = await getInfoIcon(page, 'Views') // Trigger tooltip await infoIcon.hover() @@ -356,7 +354,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should associate tooltip with trigger via aria-describedby', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Instant Booking') + const infoIcon = await getInfoIcon(page, 'Max Rollover') // Hover to show tooltip await infoIcon.hover() @@ -378,94 +376,84 @@ test.describe('Subscription Tier Tooltips', () => { }) test.describe('Tooltip Content Validation', () => { - test('should display correct tooltip for Messages per Month', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Messages per Month') + test('should display correct tooltip for Messages', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Messages') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Messages per Month']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Messages']) }) - test('should display correct tooltip for Provider Discoveries/Month', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Provider Discoveries/Month') + test('should display correct tooltip for Discoveries', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Discoveries') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Provider Discoveries/Month']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Discoveries']) }) - test('should display correct tooltip for Profile Views/Month', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Views/Month') + test('should display correct tooltip for Views', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Views') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Profile Views/Month']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Views']) }) - test('should display correct tooltip for Search Quality', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Search Quality') + test('should display correct tooltip for Discovery Memory', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Discovery Memory') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Search Quality']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Discovery Memory']) }) - test('should display correct tooltip for Profile Detail', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Detail') + test('should display correct tooltip for Rollover Policy', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Rollover Policy') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Profile Detail']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Rollover Policy']) }) - test('should display correct tooltip for Advanced Filters', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Advanced Filters') + test('should display correct tooltip for Max Rollover', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Max Rollover') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Advanced Filters']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Max Rollover']) }) - test('should display correct tooltip for Verified Badge Filter', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Verified Badge Filter') + test('should display correct tooltip for Verification', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Verification') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Verified Badge Filter']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Verification']) }) - test('should display correct tooltip for Instant Booking', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Instant Booking') + test('should display correct tooltip for Concierge', async ({ page }) => { + const infoIcon = await getInfoIcon(page, 'Concierge') await infoIcon.hover() const tooltip = await waitForTooltip(page) const content = await tooltip.textContent() - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Instant Booking']) - }) - - test('should display correct tooltip for Customer Support', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Customer Support') - await infoIcon.hover() - - const tooltip = await waitForTooltip(page) - const content = await tooltip.textContent() - - expect(content?.trim()).toBe(TOOLTIP_CONTENT['Customer Support']) + expect(content?.trim()).toBe(TOOLTIP_CONTENT['Concierge']) }) test('should validate all tooltip content matches implementation', async ({ page }) => { @@ -505,7 +493,7 @@ test.describe('Subscription Tier Tooltips', () => { test.describe('Visual Positioning', () => { test('should position tooltip to the right of Info icon', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Messages per Month') + const infoIcon = await getInfoIcon(page, 'Messages') await infoIcon.hover() const tooltip = await waitForTooltip(page) @@ -526,7 +514,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should not overlap table content', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Detail') + const infoIcon = await getInfoIcon(page, 'Verification') await infoIcon.hover() const tooltip = await waitForTooltip(page) @@ -544,8 +532,8 @@ test.describe('Subscription Tier Tooltips', () => { test.describe('Tooltip State Management', () => { test('should show only one tooltip at a time', async ({ page }) => { - const firstIcon = await getInfoIcon(page, 'Messages per Month') - const secondIcon = await getInfoIcon(page, 'Search Quality') + const firstIcon = await getInfoIcon(page, 'Messages') + const secondIcon = await getInfoIcon(page, 'Discovery Memory') // Show first tooltip await firstIcon.hover() @@ -565,7 +553,7 @@ test.describe('Subscription Tier Tooltips', () => { }) test('should persist tooltip while hovering over tooltip itself', async ({ page }) => { - const infoIcon = await getInfoIcon(page, 'Profile Detail') + const infoIcon = await getInfoIcon(page, 'Verification') await infoIcon.hover() const tooltip = await waitForTooltip(page)