mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-13 16:04:36 -04:00
160 lines
4.8 KiB
TypeScript
160 lines
4.8 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import translations from '../client/i18n/locales/english/translations.json';
|
|
|
|
// A challenge in a non-legacy public superblock (responsive-web-design-v9)
|
|
const challengePath =
|
|
'/learn/responsive-web-design-v9/workshop-cat-photo-app/step-3';
|
|
|
|
// A challenge in a non-public superblock — modal should not appear
|
|
const nonPublicChallengePath =
|
|
'/learn/foundational-c-sharp-with-microsoft/write-your-first-code-using-c-sharp/write-your-first-c-sharp-code';
|
|
|
|
const STORE_KEY = 'mobileAppModalDismissedAt';
|
|
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
|
const mobileAppModal = translations['mobile-app-modal'] as Record<
|
|
string,
|
|
string
|
|
>;
|
|
|
|
test.describe('Mobile App Modal', () => {
|
|
test.skip(
|
|
({ isMobile }) => isMobile === false,
|
|
'Skip on desktop — modal only renders on mobile viewports'
|
|
);
|
|
|
|
test('shows the modal on a public superblock challenge', async ({ page }) => {
|
|
await page.goto(challengePath);
|
|
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).toBeVisible();
|
|
await expect(page.getByText(mobileAppModal.body)).toBeVisible();
|
|
await expect(page.getByRole('link')).toBeVisible();
|
|
});
|
|
|
|
test('link points to an app store', async ({ page }) => {
|
|
await page.goto(challengePath);
|
|
|
|
const link = page.getByRole('link');
|
|
const href = await link.getAttribute('href');
|
|
expect(
|
|
href ===
|
|
'https://apps.apple.com/us/app/freecodecamp/id6446908151?itsct=apps_box_link&itscg=30200' ||
|
|
href ===
|
|
'https://play.google.com/store/apps/details?id=org.freecodecamp'
|
|
).toBeTruthy();
|
|
});
|
|
|
|
test('closing with X hides the modal but shows again on next page load', async ({
|
|
page
|
|
}) => {
|
|
await page.goto(challengePath);
|
|
await page
|
|
.getByRole('button', { name: translations.buttons.close })
|
|
.click();
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
|
|
// No timestamp stored — modal reappears on next navigation
|
|
const stored = await page.evaluate(
|
|
key => localStorage.getItem(key),
|
|
STORE_KEY
|
|
);
|
|
expect(stored).toBeNull();
|
|
|
|
await page.goto(
|
|
'/learn/responsive-web-design-v9/workshop-cat-photo-app/step-4'
|
|
);
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('clicking the store link hides the modal but shows again on next page load', async ({
|
|
page
|
|
}) => {
|
|
await page.goto(challengePath);
|
|
await page.getByRole('link').click();
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
|
|
const stored = await page.evaluate(
|
|
key => localStorage.getItem(key),
|
|
STORE_KEY
|
|
);
|
|
expect(stored).toBeNull();
|
|
});
|
|
|
|
test('"do not show me again" hides modal and stores a timestamp', async ({
|
|
page
|
|
}) => {
|
|
await page.goto(challengePath);
|
|
await page
|
|
.getByRole('button', { name: mobileAppModal['do-not-show'] })
|
|
.click();
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
|
|
const stored = await page.evaluate(
|
|
key => localStorage.getItem(key),
|
|
STORE_KEY
|
|
);
|
|
expect(stored).not.toBeNull();
|
|
});
|
|
|
|
test('does not show after "do not show" dismissal until 30 days pass', async ({
|
|
page
|
|
}) => {
|
|
// Simulate a recent dismissal
|
|
await page.addInitScript(
|
|
({ key, ts }) => localStorage.setItem(key, String(ts)),
|
|
{ key: STORE_KEY, ts: Date.now() - 1000 }
|
|
);
|
|
await page.goto(challengePath);
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
});
|
|
|
|
test('shows again after 30 days have passed since "do not show"', async ({
|
|
page
|
|
}) => {
|
|
// Simulate a dismissal more than 30 days ago
|
|
await page.addInitScript(
|
|
({ key, ts }) => localStorage.setItem(key, String(ts)),
|
|
{ key: STORE_KEY, ts: Date.now() - THIRTY_DAYS_MS - 1000 }
|
|
);
|
|
await page.goto(challengePath);
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('does not show on a non-public superblock challenge', async ({
|
|
page
|
|
}) => {
|
|
await page.goto(nonPublicChallengePath);
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Mobile App Modal — desktop', () => {
|
|
test.skip(
|
|
({ isMobile }) => isMobile === true,
|
|
'Skip on mobile — this suite verifies the modal is absent on desktop'
|
|
);
|
|
|
|
test('does not show on desktop', async ({ page }) => {
|
|
await page.goto(challengePath);
|
|
await expect(
|
|
page.getByRole('dialog', { name: mobileAppModal.heading })
|
|
).not.toBeVisible();
|
|
});
|
|
});
|