chore(mock-api): 🔧 Update server.js and add/fix mock API test cases in tier-tooltips.spec.ts

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-01-31 15:30:05 -08:00
parent 4ebfad6339
commit 3d497385f6
2 changed files with 183 additions and 126 deletions

View file

@ -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)

View file

@ -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)