302 lines
10 KiB
TypeScript
302 lines
10 KiB
TypeScript
import {WhatsNewBanner, WhatsNewMenu} from '@/admin-pages';
|
|
import {expect, test} from '@/helpers/playwright/fixture';
|
|
import type {Page} from '@playwright/test';
|
|
|
|
// Local type definition matching the API response format
|
|
type RawChangelogEntry = {
|
|
slug: string;
|
|
title: string;
|
|
custom_excerpt: string;
|
|
published_at: string;
|
|
url: string;
|
|
featured: string;
|
|
feature_image?: string;
|
|
html?: string;
|
|
};
|
|
|
|
function daysAgo(days: number): Date {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() - days);
|
|
return date;
|
|
}
|
|
|
|
function daysFromNow(days: number): Date {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() + days);
|
|
return date;
|
|
}
|
|
|
|
function createEntry(publishedAt: Date, options: {
|
|
featured?: boolean;
|
|
title?: string;
|
|
excerpt?: string;
|
|
feature_image?: string;
|
|
} = {}): RawChangelogEntry {
|
|
const title = options.title ?? 'Test Update';
|
|
const slug = title.toLowerCase().replace(/\s+/g, '-');
|
|
return {
|
|
slug,
|
|
title,
|
|
custom_excerpt: options.excerpt ?? 'Test feature',
|
|
published_at: publishedAt.toISOString(),
|
|
url: `https://ghost.org/changelog/${slug}`,
|
|
featured: (options.featured ?? false) ? 'true' : 'false',
|
|
...(options.feature_image && {feature_image: options.feature_image})
|
|
};
|
|
}
|
|
|
|
async function mockChangelog(page: Page, entries: RawChangelogEntry[]): Promise<void> {
|
|
await page.route('https://ghost.org/changelog.json', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
posts: entries,
|
|
changelogUrl: 'https://ghost.org/changelog/'
|
|
})
|
|
});
|
|
});
|
|
}
|
|
|
|
test.describe('Ghost Admin - What\'s New', () => {
|
|
test.describe('banner notification', () => {
|
|
test('shows banner for new entries the user has not seen', async ({page}) => {
|
|
await mockChangelog(page, [
|
|
createEntry(daysFromNow(1), {
|
|
title: 'New Update',
|
|
excerpt: 'This is an exciting new feature'
|
|
})
|
|
]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await expect(banner.container).toBeVisible();
|
|
await expect(banner.title).toHaveText('New Update');
|
|
await expect(banner.excerpt).toHaveText('This is an exciting new feature');
|
|
});
|
|
|
|
test('does not show banner for entries from before user joined', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysAgo(30))]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test('does not show banner when there are no entries', async ({page}) => {
|
|
await mockChangelog(page, []);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test.describe('dismissal behavior', () => {
|
|
test('hides banner immediately when close button is clicked', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await expect(banner.container).toBeVisible();
|
|
|
|
await banner.dismiss();
|
|
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test('hides banner immediately when link is clicked', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await expect(banner.container).toBeVisible();
|
|
|
|
await banner.clickLinkAndClosePopup();
|
|
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test('hides banner immediately when modal is opened', async ({page}) => {
|
|
await mockChangelog(page, [
|
|
createEntry(daysFromNow(1), {feature_image: 'https://ghost.org/image1.jpg'}),
|
|
createEntry(daysAgo(5))
|
|
]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
const menu = new WhatsNewMenu(page);
|
|
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await expect(banner.container).toBeVisible();
|
|
|
|
const modal = await menu.openWhatsNewModal();
|
|
await modal.close();
|
|
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test('banner remains hidden after reload when dismissed', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await banner.dismiss();
|
|
|
|
await banner.goto();
|
|
await expect(banner.container).toBeHidden();
|
|
});
|
|
|
|
test('banner reappears when a new entry is published after dismissal', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const banner = new WhatsNewBanner(page);
|
|
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
await banner.dismiss();
|
|
|
|
await banner.goto();
|
|
await expect(banner.container).toBeHidden();
|
|
|
|
await mockChangelog(page, [
|
|
createEntry(daysFromNow(2), {
|
|
title: 'Second Update'
|
|
})
|
|
]);
|
|
|
|
await banner.goto();
|
|
await banner.waitForBanner();
|
|
|
|
await expect(banner.container).toBeVisible();
|
|
await expect(banner.title).toHaveText('Second Update');
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('modal', () => {
|
|
test('shows modal with all entries when opened from user menu', async ({page}) => {
|
|
await mockChangelog(page, [
|
|
createEntry(daysFromNow(1), {
|
|
title: 'Latest Update',
|
|
excerpt: 'Latest feature',
|
|
feature_image: 'https://ghost.org/image1.jpg'
|
|
}),
|
|
createEntry(daysAgo(5), {
|
|
title: 'Previous Update',
|
|
excerpt: 'Previous feature'
|
|
})
|
|
]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
const modal = await menu.openWhatsNewModal();
|
|
|
|
await expect(modal.modal).toBeVisible();
|
|
await expect(modal.title).toBeVisible();
|
|
|
|
const entries = await modal.getEntries();
|
|
expect(entries.length).toBe(2);
|
|
|
|
expect(entries[0].title).toBe('Latest Update');
|
|
expect(entries[0].excerpt).toBe('Latest feature');
|
|
expect(entries[0].hasImage).toBe(true);
|
|
|
|
expect(entries[1].title).toBe('Previous Update');
|
|
expect(entries[1].excerpt).toBe('Previous feature');
|
|
});
|
|
});
|
|
|
|
test.describe('badge indicators', () => {
|
|
test('shows badge for new non-featured entries the user has not seen', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
await expect(menu.avatarBadge).toBeVisible();
|
|
});
|
|
|
|
test('shows badge in user menu when there are new entries', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
await menu.openUserMenu();
|
|
|
|
await expect(menu.menuBadge).toBeVisible();
|
|
});
|
|
|
|
test('does not show badges for entries from before user joined', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysAgo(30))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
await expect(menu.avatarBadge).toBeHidden();
|
|
|
|
await menu.openUserMenu();
|
|
await expect(menu.menuBadge).toBeHidden();
|
|
});
|
|
|
|
test.describe('dismissal behavior', () => {
|
|
test('hides badges immediately when What\'s new modal is opened', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
await expect(menu.avatarBadge).toBeVisible();
|
|
|
|
const modal = await menu.openWhatsNewModal();
|
|
await modal.close();
|
|
|
|
await expect(menu.avatarBadge).toBeHidden();
|
|
});
|
|
|
|
test('badges remain hidden after reload when What\'s new has been viewed', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
const modal = await menu.openWhatsNewModal();
|
|
await modal.close();
|
|
|
|
await menu.goto();
|
|
await expect(menu.avatarBadge).toBeHidden();
|
|
});
|
|
|
|
test('badges reappear when a new entry is published after viewing', async ({page}) => {
|
|
await mockChangelog(page, [createEntry(daysFromNow(1))]);
|
|
|
|
const menu = new WhatsNewMenu(page);
|
|
await menu.goto();
|
|
|
|
const modal = await menu.openWhatsNewModal();
|
|
await modal.close();
|
|
|
|
await menu.goto();
|
|
await expect(menu.avatarBadge).toBeHidden();
|
|
|
|
await mockChangelog(page, [createEntry(daysFromNow(2))]);
|
|
|
|
await menu.goto();
|
|
|
|
await expect(menu.avatarBadge).toBeVisible();
|
|
});
|
|
});
|
|
});
|
|
});
|