fix bug 16 and 17
Build & Push / Pipeline Tests (push) Successful in 1m53s
Test / Type Check (all packages) (push) Successful in 56s
Build & Push / Build & Push Docker Image (push) Failing after 4m53s
Test / API Unit Tests (push) Successful in 1m15s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 41s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 42s
Test / API Integration Tests (push) Successful in 1m6s
Build & Push / Pipeline Tests (push) Successful in 1m53s
Test / Type Check (all packages) (push) Successful in 56s
Build & Push / Build & Push Docker Image (push) Failing after 4m53s
Test / API Unit Tests (push) Successful in 1m15s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 41s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 42s
Test / API Integration Tests (push) Successful in 1m6s
This commit is contained in:
@@ -62,6 +62,11 @@ test('pricing recommends a plan from the selected fleet band', async ({ page })
|
||||
'data-recommended',
|
||||
'true',
|
||||
);
|
||||
await page.getByLabel('76–150 vehicles').check();
|
||||
await expect(page.locator('#pricing [data-plan="pro"]')).toHaveAttribute(
|
||||
'data-recommended',
|
||||
'true',
|
||||
);
|
||||
await page.getByLabel('Annual', { exact: true }).check();
|
||||
await expect(page.getByLabel('Annual', { exact: true })).toBeChecked();
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('homepage content model', () => {
|
||||
expect(content.modules.items).toHaveLength(6);
|
||||
expect(content.faq.items).toHaveLength(6);
|
||||
expect(content.pricing.fleetBands).toHaveLength(4);
|
||||
expect(content.pricing.plans).toHaveLength(3);
|
||||
expect(content.pricing.plans).toHaveLength(4);
|
||||
expect(content.hero.preview.rows).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@ describe('homepage content model', () => {
|
||||
it('includes explicit theme handoff in account creation URLs', () => {
|
||||
expect(accountCreateUrl('en', 'light')).toBe('/dashboard/sign-up?lang=en&theme=light');
|
||||
expect(accountCreateUrl('en', 'dark')).toBe('/dashboard/sign-up?lang=en&theme=dark');
|
||||
expect(accountCreateUrl('en', 'dark', 'GROWTH')).toBe(
|
||||
'/dashboard/sign-up?lang=en&theme=dark&plan=GROWTH',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps preview identifiers isolated from translated prose', () => {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { DemoDialogHost } from '@/components/integrations/DemoDialogHost';
|
||||
import { demoOpenEventName } from '@/components/integrations/DemoTrigger';
|
||||
import enHomepage from '@/content/locales/en/homepage.json';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
function openDemoForm(source: 'hero' | 'pricing') {
|
||||
const trigger = document.createElement('button');
|
||||
document.body.appendChild(trigger);
|
||||
act(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(demoOpenEventName, {
|
||||
detail: { source, trigger },
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
describe('DemoDialogHost', () => {
|
||||
beforeAll(() => {
|
||||
HTMLDialogElement.prototype.showModal ??= function showModal() {
|
||||
this.setAttribute('open', '');
|
||||
};
|
||||
HTMLDialogElement.prototype.close ??= function close() {
|
||||
this.removeAttribute('open');
|
||||
};
|
||||
});
|
||||
|
||||
it('keeps the full fleet selector for regular demo requests', () => {
|
||||
render(<DemoDialogHost locale="en" messages={enHomepage.form} enabled />);
|
||||
|
||||
openDemoForm('hero');
|
||||
|
||||
const fleet = screen.getByLabelText(/Approximate fleet size/) as HTMLSelectElement;
|
||||
expect(Array.from(fleet.options).map((option) => option.textContent)).toEqual([
|
||||
'Select an option',
|
||||
'1–25 vehicles',
|
||||
'26–75 vehicles',
|
||||
'76–150 vehicles',
|
||||
'150 or more vehicles',
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits the pricing request fleet selector to 150 or more vehicles', () => {
|
||||
render(<DemoDialogHost locale="en" messages={enHomepage.form} enabled />);
|
||||
|
||||
openDemoForm('pricing');
|
||||
|
||||
const fleet = screen.getByLabelText(/Approximate fleet size/) as HTMLSelectElement;
|
||||
expect(Array.from(fleet.options).map((option) => option.textContent)).toEqual([
|
||||
'150 or more vehicles',
|
||||
]);
|
||||
expect(fleet.value).toBe('250+');
|
||||
expect(screen.getByRole('button', { name: 'Request quote' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Create my account' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog', { name: 'Request Enterprise quote' })).toBeVisible();
|
||||
expect(screen.getByText('Enterprise quote')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Share company information only/)).toBeInTheDocument();
|
||||
expect(screen.queryByText('Safe local adapter')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Non-production mode validates/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -15,7 +15,13 @@ describe('PricingSection', () => {
|
||||
const user = userEvent.setup();
|
||||
const content = buildHomepageContent(enHomepage, enShell).pricing;
|
||||
const { container } = render(
|
||||
<PricingSection content={content} locale="en" homePath="/en/system" demoSubmissionEnabled />,
|
||||
<PricingSection
|
||||
content={content}
|
||||
locale="en"
|
||||
themePreference="light"
|
||||
homePath="/en/system"
|
||||
demoSubmissionEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector('[data-plan="launch"]')).toHaveAttribute(
|
||||
@@ -30,15 +36,71 @@ describe('PricingSection', () => {
|
||||
'true',
|
||||
);
|
||||
expect(container.querySelector('[data-plan="launch"]')).not.toHaveAttribute('data-recommended');
|
||||
|
||||
await user.click(screen.getByLabelText('76–150 vehicles'));
|
||||
|
||||
expect(container.querySelector('[data-plan="pro"]')).toHaveAttribute(
|
||||
'data-recommended',
|
||||
'true',
|
||||
);
|
||||
expect(container.querySelector('[data-plan="growth"]')).not.toHaveAttribute('data-recommended');
|
||||
});
|
||||
|
||||
it('keeps unapproved public prices out of the rendered section', () => {
|
||||
it('renders approved public prices except for Enterprise custom pricing', async () => {
|
||||
const user = userEvent.setup();
|
||||
const content = buildHomepageContent(enHomepage, enShell).pricing;
|
||||
render(
|
||||
<PricingSection content={content} locale="en" homePath="/en/system" demoSubmissionEnabled />,
|
||||
<PricingSection
|
||||
content={content}
|
||||
locale="en"
|
||||
themePreference="light"
|
||||
homePath="/en/system"
|
||||
demoSubmissionEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getAllByText('Starting price pending approval')).toHaveLength(2);
|
||||
expect(screen.queryByText('Starting price pending approval')).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/149/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/299/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/399/)).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom pricing')).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByLabelText(/Annual/));
|
||||
|
||||
expect(screen.getByText(/Save 20%/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/1,430/)).toBeInTheDocument();
|
||||
expect(screen.getAllByText('/ year')).toHaveLength(3);
|
||||
expect(screen.getAllByText('billed annually after full-payment discount')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('links self-serve plan CTAs to account creation and opens a pricing form for Enterprise', () => {
|
||||
const content = buildHomepageContent(enHomepage, enShell).pricing;
|
||||
render(
|
||||
<PricingSection
|
||||
content={content}
|
||||
locale="en"
|
||||
themePreference="dark"
|
||||
homePath="/en/system"
|
||||
demoSubmissionEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Estimate Launch pricing' })).toHaveAttribute(
|
||||
'href',
|
||||
'/dashboard/sign-up?lang=en&theme=dark&plan=STARTER',
|
||||
);
|
||||
expect(screen.getByRole('link', { name: 'Book a Growth demo' })).toHaveAttribute(
|
||||
'href',
|
||||
'/dashboard/sign-up?lang=en&theme=dark&plan=GROWTH',
|
||||
);
|
||||
expect(screen.getByRole('link', { name: 'Book a Pro demo' })).toHaveAttribute(
|
||||
'href',
|
||||
'/dashboard/sign-up?lang=en&theme=dark&plan=PRO',
|
||||
);
|
||||
expect(screen.getByRole('button', { name: 'Talk to sales' })).toHaveAttribute(
|
||||
'data-demo-source',
|
||||
'pricing',
|
||||
);
|
||||
expect(screen.queryByRole('link', { name: 'Talk to sales' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,13 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('homepage pricing configuration', () => {
|
||||
it('keeps commercial values in one guarded configuration', () => {
|
||||
expect(pricingCommercialConfig.publicPricesApproved).toBe(false);
|
||||
expect(pricingCommercialConfig.publicPricesApproved).toBe(true);
|
||||
expect(pricingCommercialConfig.currency).toBe('MAD');
|
||||
expect(pricingCommercialConfig.annualDiscountPercent).toBeNull();
|
||||
expect(pricingCommercialConfig.annualDiscountPercent).toBe(20);
|
||||
expect(pricingCommercialConfig.monthlyPrices.launch.small).toBe(149);
|
||||
expect(pricingCommercialConfig.monthlyPrices.growth.growing).toBe(299);
|
||||
expect(pricingCommercialConfig.monthlyPrices.pro.scale).toBe(399);
|
||||
expect(pricingCommercialConfig.monthlyPrices.enterprise.enterprise).toBeNull();
|
||||
});
|
||||
|
||||
it('maps every fleet band and plan from localized content', () => {
|
||||
@@ -20,6 +24,6 @@ describe('homepage pricing configuration', () => {
|
||||
'scale',
|
||||
'enterprise',
|
||||
]);
|
||||
expect(pricing.plans.map((plan) => plan.id)).toEqual(['launch', 'growth', 'enterprise']);
|
||||
expect(pricing.plans.map((plan) => plan.id)).toEqual(['launch', 'growth', 'pro', 'enterprise']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
import { currentThemePreference } from '@/lib/theme/client';
|
||||
import { currentThemePreference, persistTheme } from '@/lib/theme/client';
|
||||
import { isThemePreference, resolveTheme, serverResolvedTheme } from '@/lib/theme/config';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
let storage: Map<string, string>;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = new Map<string, string>();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
configurable: true,
|
||||
value: {
|
||||
clear: () => storage.clear(),
|
||||
getItem: (key: string) => storage.get(key) ?? null,
|
||||
removeItem: (key: string) => storage.delete(key),
|
||||
setItem: (key: string, value: string) => storage.set(key, value),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
storage.clear();
|
||||
document.cookie = 'hpc-theme=; Path=/; Max-Age=0';
|
||||
document.cookie = 'rentaldrivego-theme=; Path=/; Max-Age=0';
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('theme foundations', () => {
|
||||
it('accepts only approved preferences', () => {
|
||||
@@ -21,4 +43,26 @@ describe('theme foundations', () => {
|
||||
delete document.documentElement.dataset.themePreference;
|
||||
expect(currentThemePreference()).toBe('dark');
|
||||
});
|
||||
|
||||
it('persists the shared dashboard theme when homepage theme changes', () => {
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: false } as MediaQueryList);
|
||||
|
||||
persistTheme('dark');
|
||||
|
||||
expect(window.localStorage.getItem('hpc.theme.preference')).toBe('dark');
|
||||
expect(window.localStorage.getItem('rentaldrivego-theme')).toBe('dark');
|
||||
expect(document.cookie).toContain('hpc-theme=dark');
|
||||
expect(document.cookie).toContain('rentaldrivego-theme=dark');
|
||||
});
|
||||
|
||||
it('stores the resolved shared theme for system preference', () => {
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: false } as MediaQueryList);
|
||||
|
||||
persistTheme('system');
|
||||
|
||||
expect(window.localStorage.getItem('hpc.theme.preference')).toBe('system');
|
||||
expect(window.localStorage.getItem('rentaldrivego-theme')).toBe('light');
|
||||
expect(document.cookie).toContain('hpc-theme=system');
|
||||
expect(document.cookie).toContain('rentaldrivego-theme=light');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user